﻿
/*
EC-IDE class, from default dom template,
	SyntaxEditor
	org.supertree.editor.SyntaxEditor

Author: Feng Weiguo ( 冯卫国 )
Mail: forxm@21cn.com
Web: www.supertree.org

All Rights Reserved

LICENSE: LGPL

Created: 2008-11-06 09:59:06

Updated: 2008-12-10

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License (LGPL) as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.
*/

//#EC_PARAM $org.supertree.editor.SyntaxViewer {bind:'s',debug:'b'}
//bind: a dom element access by $ec.$(), when click on the binding element, the editor will be active.
//debug: debug mode, show input caret and last tmp line

$ec.$preload(
	"org.supertree.editor.SyntaxEditor",
	function()
	{
		this.prototype= $ec.$derive(
			"org.supertree.editor.SyntaxViewer",
			{
//////////////////////////////
// EC-CLASS BODY
//////////////////////////////
			
			//////////////////
			//sub element variable
			//////////////////
			
			eidCaret: "",	//the caret, <textarea>
			
			eidBlock1: "",  //block is used to show selection
			eidBlock2: "",
			eidBlock3: "",
			
			//////////////////
			//focus variable
			//////////////////
			
			eidBind: "",	//for focus
			
			bFocus: false,  //focus flag
			
			funcGlobalMouseDown: null,	//dynamic global event binding, for bur
			
			//////////////////
			//view variable
			//////////////////
			
			bOverwrite: 0,	//overwrite mode flag

			bDebug: 0,	//debug mode flag

			sFind: "",		//last find string
			bFindCase: false,	//last find case
			
			bScrolling: 0,	//scrolling flag, for ie

			//////////////////
			//block selection variable
			//////////////////
			
			pos: null,      //block head
			pos2: null,     //block end, current caret; if nLine==-1, same as pos
			
			//////////////////
			//mouse operating variable
			//////////////////
			
			bMousedown: false,	//mouse down flag
			
			nMouseMode: 0,  //0: none, 1: select, 2: drag, 3:focus, 4:scroll -1: cancel
			
			funcGlobalMouseUp: null,	//dynamic global event binding, for mouse down capture
			
			posCur: null,   //temporary position for current position checking
			posMove: null,	//for mouse moving position calculation while dragging
			
			bDragCopy: 0,	//drag copy flag
			
			//////////////////
			//keyboard operating variable
			//////////////////
			
			bPreventKey: true,		//prevent keyboard event flag
			
			nVertCol: 0,	//save last col when use Up/Down key, 0: invalid
			
			sKeyCmd: "",	//async keyboard command flag
			
			bLockCaret: false,		//for keyboard Copy/Cut/Paste (Ctrl+C/X/V) operation
			
			//some browser (safari,chrome) will not take \xA0 as \s
			
			regGroupHead: /(^\w+)|(^[\s\u00A0]+)|(^[\u2E80-\uFFFD]+)|(^[^\w\s\u00A0\u2E80-\uFFFD]+)/ ,	//char group at string head
			regGroupEnd: /(\w+$)|([\s\u00A0]+$)|([\u2E80-\uFFFD]+$)|([^\w\s\u00A0\u2E80-\uFFFD]+$)/ ,		//char group at string end
			
			regGetHeadSpace: /^([\s\u00A0]*).*$/ ,		//for str.replace( regGetHeadSpace , "$1" );
			
			//////////////////
			//data operating variable
			//////////////////
			
			aOp: null,  //array buffer to save operation, each item is an array [sKey,nLine,nCol,sDel,sIns]
			nOpIdx: 0,  //op buffer index
			
			bModified: false,   //modify flag
			
			//////////////////
			//input editing variable
			//////////////////
			
			sEditing: "",	//editing string
			
			posEdit: null,	//position start input
			
			//////////////////
			//for SyntaxEditor
			//////////////////
			
			bEditor: 1,
			
			//////////////////
			//constructor
			//////////////////
			
			SyntaxEditor: function( e, p )    //ec-class constructor
			{
				//this style
				e.style.border="1px solid lightgrey";
				e.style.position="relative";
				e.style.overflowX="scroll";
				e.style.overflowY="scroll";
				//e.tabIndex=-1;
				e.style.zIndex=1;
				
				//sub element
				var ei,eiText;
				eiText= $ec(this.eidText);
				
				this.eidBlock1= $ec.$eid( ei=$ec(e,"block1",eiText) );
				this.eidBlock2= $ec.$eid( ei=$ec(e,"block2",ei) );
				this.eidBlock3= $ec.$eid( ei=$ec(e,"block3",ei) );
				
				var eCaret= $ec(e,"caret",ei);
				this.eidCaret= $ec.$eid( eCaret );
				eCaret.tabIndex=-1;
				
				//prepare block selection
				this.pos=[0,0,0,0];
				this.pos2=[-1,0,0,0];
				this.posCur=[];
				this.posMove=[];
				this.posEdit=[];
				
				//other
				this.aOp= [];
				
				//parameters
				if(p)
				{
					if(typeof p.debug!="undefined" ) this.SetDebugMode( p.debug );
				}
				
				//update view
				this.updateLineNumber(1);
				
				var eBind;
				if(p && p.bind) eBind= $ec.$(e,p.bind);
				if(!eBind) eBind=e;
				this.eidBind=$ec.$eid(eBind);
				
				//["","mousedown","OnMousedown"],
				$ec.addEvt(eBind,this.eid,["","mousedown","OnMousedown"]);
				
				//debug
				//this.SetText("var a,b;\n\tvar c,d;\n\nfjasdjjsad  fasjdfaslkd fasdjkf  fjasd fasjdhfkasjdf haskdjf f asdf\n\nvar c,m\nfjasdjjsad  fasjdfaslkd fasdjkf  fjasd fasjdhfkasjdf haskdjf f asdf\nvar c,d中\nsad sadf asdf fas\nsad sadf asdf fas\nsad sadf asdf fas\nsad sadf asdf fas\nsad sadf asdf fas\nsad sadf asdf fas\nsad sadf asdf fas\nsad sadf asdf fas\nsad sadf asdf fas\nsad sadf asdf fas\nsad sadf asdf fas\nsad sadf asdf fas\nsad sadf asdf fas");
				
				//special event binding, must be at the end of this ec-class
				if( window.ActiveXObject )
				{
					$ec.addEvt(eCaret,this.eid,["caret","propertychange","OnPropertyChange_ie"]);
				}
				else
				{
					$ec.addEvt(eCaret,this.eid,["caret","input","OnInput_mz"]);
				}
			},
			
			//////////////////
			//view function group
			//////////////////
			
			SetOverwrite: function( bOverwrite )
			{
				if( !bOverwrite!=!this.bOverwrite )
				{
					this.bOverwrite= bOverwrite?1:0;
					
					if( !bOverwrite ) $ec(this.eidBlock1).style.borderBottom="none";
					
					this.HideBlock();
				}
			},
			
			ToggleOverwrite: function()
			{
				this.SetOverwrite(!this.bOverwrite);
			},
			
			OnScroll: function(evt)
			{
				//$ec.TRACE("OnScroll");
				//return;
				
				var eEvt= evt.srcElement?evt.srcElement:evt.target;
				
				//if(!this.bFocus) this.OnFocus();
				
				//if(eEvt.id==this.eid && !this.bLockCaret )
				if(eEvt.id==this.eid && !this.bLockCaret && !this.sEditing && this.bFocus)
				{
					//$ec.TRACE("scrolling1");
					//this.OnBlur();
					//if( this.sAgent!="ie" && this.sAgent!="opera") window.setTimeout( "$ec('"+this.eidCaret+"').select()",0);
					if( this.sAgent!="ie" ) window.setTimeout( "$ec('"+this.eidCaret+"').select()",0);
					else
					{
						bScrolling=1;	//for ie
						//if( !this.bMousedown ) $ec(this.eidCaret).select();
						$ec(this.eidCaret).select();
						//$ec.TRACE("ie scrolling");
						//window.setTimeout( "$ec('"+this.eidCaret+"').select()",0);
						this.bMousedown=false;
						//if( this.bMousedown ) $ec(this.eidCaret).select();
					}
					
					if(this.sAgent=="opera" ) $ec(this.eidCaret).focus();
					
					//$ec.TRACE("scrolling2");
				}
				
			},
			
			SetDebugMode: function( bDebug )
			{
				this.bDebug= bDebug?1:0;
				
				this.getTmpLine().style.visibility=this.bDebug?"":"hidden";
				
				var ei= $ec(this.eidCaret);
				ei.style.border=this.bDebug?"1px green solid":"none";
			},
			
			ToggleDebugMode: function()
			{
				this.SetDebugMode(!this.bDebug);
			},
			
			OnFind: function()
			{
				var s;
				if( this.pos2[0]!=-1) s= this.GetBlockString([]).join("\n");
				else s= this.sFind;
				
				var s=window.prompt("Find",s );
				
				if(s)
				{
					this.sFind=s;
					this.FindString(s);
				}
			},
			
			FindNext: function(bBack)
			{
				if(this.sFind ) this.FindString(this.sFind,this.bFindCase,bBack);
			},
			
			//FindString: function( s [, bCase [, bBack ]] )
			//return true/false
			FindString: function( s, bCase, bBack )
			{
				if(!s) return false;
				
				this.OnFocus();
				
				this.bFindCase= bCase?1:0;	//save for F3/FindNext
				
				if(!bCase) s=s.toLowerCase();
				
				//if(this.pos2[0]!=-1) this._collapseBlock(!bBack);
				if(this.pos2[0]!=-1) this.collapseBlock();
				
				var ei= this._line();
				var str=this.getTextContent(ei).replace( /\u00A0/g, " ");
				var j,k=this.pos[0];
				
				//first
				//j=this.pos[1]-s.length;
				j=bBack?(this.pos[1]-1):(this.pos[1]+1);
				if(j>=0 && j<str.length)
				{
					//if( bCase ) j= bBack?str.lastIndexOf(s,j):str.indexOf(s,this.pos[1]);
					//else j= bBack?str.toLowerCase().lastIndexOf(s,j):str.toLowerCase().indexOf(s,this.pos[1]);
					if( bCase ) j= bBack?str.lastIndexOf(s,j):str.indexOf(s,j);
					else j= bBack?str.toLowerCase().lastIndexOf(s,j):str.toLowerCase().indexOf(s,j);
					
					//$ec.STATUS(this.pos+" "+bBack+" "+j);
					
					if( j>=0 )
					{
						this.SetBlock( k, j, k, j+s.length );
						this.UpdateCaret(1);
						return true;
					}
				}
				
				while(ei=(bBack?ei.previousSibling:ei.nextSibling))
				{
					if( ei.id==this.eidTmp ) break;
					
					k=bBack?(k-1):(k+1);
					str= this.getTextContent(ei).replace( /\u00A0/g, " ");
					
					if( bCase ) j= bBack?str.lastIndexOf(s):str.indexOf(s);
					else j= bBack?str.toLowerCase().lastIndexOf(s):str.toLowerCase().indexOf(s);
					
					if( j>=0 )
					{
						this.SetBlock( k, j, k, j+s.length );
						this.UpdateCaret(1);
						return true;
					}
				}
				
				return false;
			},
			
			ToggleBookmark: function()
			{
				var ei=this._line();
				
				//ei.style.backgroundColor=(ei.style.backgroundColor)?"":"#CCFFCC";
				//ei.style.borderLeft=(ei.style.borderLeftColor)?"none":"1px solid gray";
				if( ei.style.backgroundImage && ei.style.backgroundImage!="none" )
				{
					ei.style.backgroundImage="none";
				}
				else
				{
					ei.style.backgroundImage= "url("+$ec.$file["Bookmark_gif"]+")";
					ei.style.backgroundRepeat= "no-repeat";
				}
			},
			
			//NextBookmark: function([bBack])
			NextBookmark: function(bBack)
			{
				this.OnFocus();
				
				var j= this.pos[0];
				var ei=this._line(j);
				while(ei=bBack?ei.previousSibling:ei.nextSibling)
				{
					j=bBack?(j-1):(j+1);
					
					//if( ei.style.backgroundColor )
					if( ei.style.backgroundImage && ei.style.backgroundImage!="none" )
					{
						this.SetBlock(j,0,-1);
						this.UpdateCaret(1);
						return;
					}
				}
			},
			
			//////////////////
			//mouse function group
			//////////////////
			
			captureMouse: function()
			{
				/*
				var func= this.funcGlobalMouseUp;
				
				//if(func && this.sAgent=="firefox" ) return;
				
				if( !func ) func= this.funcGlobalMouseUp= $ec.evtFunc(this.eid,"OnGlobalMouseup");
				
				if(document.attachEvent) document.attachEvent( "onmouseup", func );
				else document.addEventListener( "mouseup", func, false );
				
				//$ec.TRACE("captureMouse "+!!func);
				*/
				
				var func= this.funcGlobalMouseUp;
				if( !func )
				{
					func= this.funcGlobalMouseUp= $ec.evtFunc(this.eid,"OnGlobalMouseup");
				
					if(document.attachEvent) document.attachEvent( "onmouseup", func );
					else document.addEventListener( "mouseup", func, false );
				}
			},
			
			releaseCapture: function()
			{
				/*
				//if( this.sAgent=="firefox" ) return;
				
				//detach global event
				var func= this.funcGlobalMouseUp;
				if(func)
				{
					if(document.detachEvent) document.detachEvent( "onmouseup", func );
					else document.removeEventListener( "onmouseup", func, false );
					//$ec.TRACE("detach global event "+!!func);
				}
				*/
				
			},
			
			startSelect: function( pos )
			{
				if(this.sAgent!="ie" && this.sAgent!="opera") this.HideCaret();
				this.HideBlock();
				this.copyPos( pos, this.pos );
				this.pos2[0]=-1;
			},
			
			//overridable function on mouse down
			CanFocus: function(evt)
			{
				return true;
			},
			
			OnMousedown: function(evt)
			{
				//return;
				
				this.nVertCol=0;
				
				//this.bScrolling=0;
				
				//$ec.TRACE("OnMousedown");
				
				if( (window.ActiveXObject && evt.button==1) || ( !window.ActiveXObject && evt.button==0 ) )
				{
					//left mouse button
					if( !this.bFocus )
					{
						this.OnFocus(evt);
						
						var eEvt= evt.srcElement?evt.srcElement:evt.target;
						if( eEvt.id==this.eid )
						{
							/*
							if( this.sAgent=="ie" )
							{
								this.bMousedown=false;
								this.nMouseMode=4;
								//this.captureMouse();
								return;
								//return this.OnPreventEvent(evt);
							}
							*/
							
							this.bMousedown=false;
							//$ec.STATUS("on scrollbar");
							return;	//on scrollbar
						}
						
						if( !this.evtPos(evt,this.posCur) )
						{
							window.setTimeout("$ec.bind['"+this.eid+"'].UpdateCaret(1)",0);
							return;
						}
						
						//window.status=this.pos+" "+this.pos2;
						if( this.pos[0]==0 && this.pos[1]==0 && this.pos2[0]==-1 )
						{
							//first time click
							
							//alert(this.posCur);
							this.startSelect(this.posCur);
							this.nMouseMode=1;
						}
						else
						{
							//$ec.TRACE("Set nMouseMode=3");
							this.nMouseMode=3;
						}
						this.bMousedown= true;
						
						this.captureMouse();
						return this.OnPreventEvent(evt,1);
					}
					else
					{
						if(this.sEditing) this.ClearInput();
						
						var eEvt= evt.srcElement?evt.srcElement:evt.target;
						//$ec.STATUS( eEvt.id );
						if( eEvt.id==this.eid )
						{
							//alert(this.sAgent);
							if( this.sAgent=="ie" || this.sAgent=="opera" )
							{
								//$ec.TRACE("ie mousedown");
								this.bMousedown=true;
								this.nMouseMode=4;
								this.captureMouse();
								return this.OnPreventEvent(evt,1);
								//return;
							}
							
							this.bMousedown= false;
							//$ec.TRACE("on scrollbar");
							return;	//on scrollbar
						}
						
						var r= this.evtPos(evt,this.posCur);
						if(!r)
						{
							//$ec.TRACE("ie !r");
							this.bMousedown=false;
							if( this.CanFocus(evt) ) window.setTimeout("$ec.bind['"+this.eid+"'].UpdateCaret(1)",0);
							return;
						}
						
						if( evt.shiftKey )
						{
							/*
							for ie6, if donn't hide caret, a system selection will show out.
							*/
							if(window.ActiveXObject) $ec(this.eidCaret).style.display="none";
							
							this.nMouseMode=-1;
							this.copyPos( this.posCur, this.pos2 );
							this.ShowBlock();
							
							//if(window.ActiveXObject) $ec(this.eidCaret).style.display="block";
							
							//this.UpdateCaret(1);
							window.setTimeout("$ec.bind['"+this.eid+"'].HideSelect();$ec('"+this.eidCaret+"').style.display='block'",0);
							//this.HideSelect();
						}
						else
						{
							//$ec.STATUS(this.posCur);
							if( this.pos2[0]!=-1 && this.isPosIn( this.posCur ) )
							{
								//drag
								//$ec.STATUS("drag start");
								this.nMouseMode=0;
							}
							else
							{
								//click
								//$ec.TRACE("ie click");
								this.startSelect(this.posCur);
								this.nMouseMode=1;
							}
						}
						
						//this.HideSelect();
						
						this.bMousedown= true;
						
						this.captureMouse();
						return this.OnPreventEvent(evt,1);
					}
				}
				else
				{
					this.bMousedown= false;
				}
				
				/*
				
				this.nMouseMode=3;
				this.bMousedown= true;
				
				this.captureMouse();
				return this.OnPreventEvent(evt);
				$ec.TRACE("OnMousedown2");
				*/
			},
			
			OnMousemove: function(evt)
			{
				//if(this.bMousedown) $ec.TRACE("OnMousemove");
				//$ec.TRACE("OnMousemove");
				
				if( this.bFocus && this.bMousedown )
				{
					if(this.nMouseMode==0)
					{
						//start drag
						this.nMouseMode=2;
						//$ec(this.eidText).style.cursor="move";
						//$ec(this.eidText).style.cursor="pointer";
						//$ec.STATUS("url("+$ec.$file["DragMove_cur"][0]+"),pointer");
						$ec(this.eidText).style.cursor="url("+$ec.$file["DragMove_cur"][0]+"),pointer";
						this.bDragCopy= 0;
					}
					else if(this.nMouseMode==1)
					{
						//$ec.TRACE("--");
						
						//$ec.TRACE("0.11");
						if( this.evtPos(evt,this.posCur) )
						{
							if( this.pos2[0]!=this.posCur[0] || this.pos2[1]!=this.posCur[1] )
							{
								//$ec.TRACE("0.12");
								this.copyPos(this.posCur,this.pos2);
								//$ec.STATUS("mouse move "+this.nMouseMode+","+this.pos+" "+this.pos2+" ");
								//this.ShowSelect();
								//this.HideSelect();
								
								//$ec.TRACE("0.13");
								this.ShowBlock();
								this.UpdateCaret();
								//$ec.TRACE("0.13b");
							}
						}
						
					}
					else if( this.nMouseMode==4 )
					{
						return;
					}
					
					if( this.nMouseMode==2 )
					{
						//$ec.STATUS("drag moving");
						var n;
						if( n=this.evtPos(evt,this.posMove) )
						{
							if(n==1) this.copyPos( this.posMove, this.posCur );
							
							this.UpdateCaret( 0, this.posCur );
							$ec(this.eidCaret).select();
							
							if( this.bDragCopy )
							{
								if( !evt.ctrlKey )
								{
									this.bDragCopy= 0;
									$ec(this.eidText).style.cursor="url("+$ec.$file["DragMove_cur"][0]+"),pointer";
								}
							}
							else
							{
								if( evt.ctrlKey )
								{
									this.bDragCopy= 1;
									$ec(this.eidText).style.cursor="url("+$ec.$file["DragCopy_cur"][0]+"),pointer";
								}
								
							}
						}
					}
					
					return this.OnPreventEvent(evt);
				}
			},
			
			//mouse up, capture global event
			OnGlobalMouseup: function(evt)
			{
				//$ec.TRACE("OnGlobalMouseup");
				
				if( this.bMousedown )
				{
					if(this.nMouseMode==0)
					{
						if( this.evtPos(evt,this.posCur) )
						{
							this.copyPos(this.posCur,this.pos);
							this.pos2[0]=-1;
							
						}
					}
					else if(this.nMouseMode==1)
					{
						//$ec.TRACE("1");
						if( this.evtPos(evt,this.posCur) )
						{
							this.copyPos(this.posCur,this.pos2);
						}
						//$ec.TRACE("2");
						
						/*
						var eLine=this._line(this.pos[0]);
						var xy= this.getAbsXY(eLine);
						//var xy= this.getRelXY(eLine.parentNode.parentNode.parentNode,eLine);
						$ec.TRACE(
							eLine.tagName+":"+ eLine.offsetLeft+","+
							eLine.parentNode.tagName+":"+eLine.parentNode.offsetLeft+","+
							eLine.parentNode.parentNode.tagName+":"+eLine.parentNode.parentNode.offsetLeft+","+
							eLine.parentNode.parentNode.parentNode.tagName+":"+eLine.parentNode.parentNode.parentNode.offsetLeft+" "+
							xy
							);
						*/
					}
					else if( this.nMouseMode==2)
					{
						$ec(this.eidText).style.cursor="auto";
						
						this.dropData( this.posCur, this.bDragCopy );
						
						this.UpdateCaret();
						$ec(this.eidCaret).select();
					}
					else if( this.nMouseMode==4 )
					{
						//$ec.TRACE("OnGlobalMouseup2");
						this.UpdateCaret(1);
						return;
					}
					/*
					*/
					
					
					//else if(this.nMouseMode==3)
					//{
						//this.UpdateCaret();
						//window.setTimeout("$ec('"+this.eid+"').UpdateCaret()",1000);
						//$ec.TRACE("nMouseMode==3");
					//}
					
					//$ec.TRACE("check nMouseMode="+this.nMouseMode);
					
					
					if(this.bScrolling)
					{
						if(this.sAgent=="ie")
						{
							this.ShowBlock();
							//this.UpdateCaret(1);
							this.bScrolling=0;
							//$ec.TRACE("sroll up" );
						}
						//$ec.STATUS("scroll mouse up");
					}
					else
					{
						//$ec.TRACE("3");
						this.ShowBlock();
						//$ec.TRACE("4");
						this.UpdateCaret(1);
						//$ec.TRACE("5");
					}
					/*
					
					this.ShowBlock();
					this.UpdateCaret(1);
					*/
					
					this.nMouseMode=0;
					this.bMousedown=false;
					
					this.releaseCapture();
					
				}
				else
				{
					//this.UpdateCaret(1);
					if(this.bFocus && this.CanFocus(evt) )
					{
						//$ec.TRACE("select");
						$ec(this.eidCaret).select();
					}
				}
			},
			
			OnDblclick: function(evt)
			{
				var s=this._lineText();
				var si=s.slice(this.pos[1]);
				if(!si) return;
				
				var mr= si.match( this.regGroupHead );
				if(!mr) return;
				var j= mr[0].length;
				var i=0;
				
				if( this.pos[1]>0 )
				{
					si=s.substring(0,this.pos[1]+1);
					mr= si.match( this.regGroupEnd );
					if( mr && mr[0] )
					{
						i=si.length-mr[0].length;
						j=si.length+j-1;
					}
				}
				
				this.SetBlock(this.pos[0],i,this.pos[0],j);
				this.UpdateCaret(1);
			},
			
			//////////////////
			//line function group
			//////////////////
			
			//_line: function([nLine])
			//return line element
			_line: function(nLine)
			{
				return $ec(this.eidText).childNodes[((typeof nLine)=="undefined")?this.pos[0]:nLine];
			},
			
			//_lineText: function([nLine])
			//return line text
			_lineText: function(nLine)
			{
				return this.getTextContent( $ec(this.eidText).childNodes[(typeof nLine=="undefined")?this.pos[0]:nLine] );
			},
			
			//return a line element from its sub element
			getLine: function(ei)
			{
				//if(!ei) alert( bug );
				
				var eBody= document.body;
				while( ei && ei!==eBody && ei.parentNode.id!=this.eidText )
				{
					ei=ei.parentNode;
				}
				return (ei && ei.parentNode.id==this.eidText)?ei:null;
			},
			
			//calculate line index by dy
			//return -1 if out of the line
			lineIndex: function(dy)
			{
				/*
				//dy=51;
				if(dy==73 )
				{
					aaa
				}
				*/
				
				//first
				if( dy<0 ) return -1;
				//if( dy<0 ) return 0;
				
				var ep= $ec(this.eidText);
				var ei1= ep.firstChild;
				if( dy<=ei1.offsetHeight ) return 0;
				var h0= ei1.offsetTop;
				var i=0;
				
				//last
				var ei2= ep.lastChild;
				var j= ep.childNodes.length-1;
				if(ei2.id==this.eidTmp )
				{
					ei2=ei2.previousSibling;
					j--;
				}
				
				if( dy>=ei2.offsetTop-h0 ) return ( dy>ei2.offsetTop-h0+ei2.offsetHeight )?-1:j;
				//if( dy>=ei2.offsetTop-h0 ) return j;
				
				if(i>=j) return i;
				
				//mid
				var k,ei,cnt=0;
				
				//$ec.TRACE("==============");
				while(1)
				{
					//k= Math.floor((j-i)*dy/(ei2.offsetTop-ei1.offsetTop));
					k= Math.floor((j-i)*(dy-ei1.offsetTop)/(ei2.offsetTop-ei1.offsetTop));
					k+=((k>0)?i:1);
					
					//$ec.TRACE(i+","+j+","+k);
					//if(k>=j) return j;
					if(k>=j) k=j;
					
					ei=ep.childNodes[k];
					
					if(dy>=ei.offsetTop-h0)
					{
						if(dy<=ei.offsetTop-h0+ei.offsetHeight) return k;
						ei1=ei;
						i=k;
					}
					else
					{
						ei2=ei.previousSibling;
						j=k-1;
						if(dy>=ei2.offsetTop-h0) return j;
					}
					if(i>=j) return i;
					
					cnt++;
					
					if(cnt>300) $ec.THROW("lineIndex>300 "+dy+" "+i+" "+j+" "+k+" "+ei2.offsetTop+" "+ei1.offsetTop);
					else if(cnt==200) $ec.TRACE("lineIndex>200 "+dy+" "+i+" "+j+" "+k+" "+ei2.offsetTop+" "+ei1.offsetTop);
					else if(cnt==100) $ec.TRACE("lineIndex>100 "+dy+" "+i+" "+j+" "+k+" "+ei2.offsetTop+" "+ei1.offsetTop);
				}
			},
			
			//calcu col index by dx/dy
			//dx/dy: offset value to line
			//pos: output position array
			colIndex: function(eLine,dx,dy,pos)
			{
				/*
				$ec.STATUS(dx+","+dy+","+eLine.offsetHeight);
				
				if(dx>200 )
				{
					aaa
				}
				*/
				
				var eTmp=this.getTmpLine();
				
				//prepare
				var s= this.bTextContent?eLine.textContent:eLine.innerText;
				var i1=0,i2=s.length,di;
				var i;
				if( this.bWrap )
				{
					i= Math.floor( (i1+i2)/2);
				}
				else
				{
					i=Math.floor( dx/this.nCharWidth );
					if(i<i1) i=i1;
					if(i>i2) i=i2;
				}
				
				var eTxt=eTmp.childNodes[0];
				var eChk=eTmp.childNodes[1];
				//var GetAbsXY=this.getAbsXY;
				var w= eChk.offsetWidth;
				var h= eChk.offsetHeight;
				//alert(h);
				//var xy= GetAbsXY(eTmp);
				//var xy= [eTmp.offsetLeft,eTmp.offsetTop]
				/*
				var xChk=eTmp.offsetLeft;
				var yChk=eTmp.offsetTop;
				*/
				var xChk=eTxt.offsetLeft;
				var yChk=eTxt.offsetTop;
				var x0=xChk,y0=yChk;
				var cnt=0;
				
				//calcu
				if( this.bWrap )
				{
					while( i!=i1 )
					{
						if(this.bTextContent) eTxt.textContent=s.substring(0,i);
						else eTxt.innerText=s.substring(0,i);
						
						//xy= GetAbsXY(eChk);
						//xy=[eChk.offsetLeft,eChk.offsetTop];
						xChk=eChk.offsetLeft;
						yChk=eChk.offsetTop;
						
						//y, for line wrap
						if( (yChk-y0)>dy )
						{
							di=Math.floor((i-i1)/2);
							if(di<1){i=i1; break;}
							i2=i;
							i=i-di;
							continue;
						}
						else if( (yChk-y0+h)<dy )
						{
							di=Math.floor((i2-i)/2);
							if(di<1){i=i2; break;}
							i1=i;
							i=i+di;
							continue;
						}
						
						//x
						if( (xChk-x0)>=dx )
						{
							di=Math.floor((i-i1)/2);
							if(di<1){i=i1; break;}
							i2=i;
							i=i-di;
							continue;
						}
						else if( (xChk+w-x0)<=dx )
						{
							di=Math.floor((i2-i)/2);
							if(di<1){i=i2; break;}
							i1=i;
							i=i+di;
							continue;
						}
						break;
					}
				}
				else
				{
					//aaa
					
					while( i!=i1 )
					{
						if(this.bTextContent) eTxt.textContent=s.substring(0,i);
						else eTxt.innerText=s.substring(0,i);
						
						if(cnt++>100) $ec.THROW("colIndex>100");

						xChk=eChk.offsetLeft;
						
						if( (xChk-x0)>=dx )
						{
							di=Math.floor((xChk-x0-dx)/w);
							//if(di<1){i=i1; break;}
							if(di<1){break;}
							i2=i;
							i=i-di;
							if(i<=0){i=0;break;}
							continue;
						}
						else if( (xChk+w-x0)<=dx )
						{
							di=Math.floor((dx-(xChk+w-x0))/w);
							//if(di<1){i=i2; break;}
							if(di<1){break;}
							i1=i;
							i=i+di;
							if(i>=i2){i=i2;break;}
							continue;
						}
						break;
					}
				}
				
				//get xy
				if(this.bTextContent) eTxt.textContent=s.substring(0,i);
				else eTxt.innerText=s.substring(0,i);
				
				xChk=eChk.offsetLeft;
				yChk=eChk.offsetTop;
				
				//and adjust, x: +1/0.5
				var j=i+1;
				if(this.bTextContent) eTxt.textContent=s.substring(0,j);
				else eTxt.innerText=s.substring(0,j);
				
				if( Math.abs(eChk.offsetLeft-eTmp.offsetLeft-dx)<Math.abs(xChk-eTmp.offsetLeft-dx) )
				{
					i=j;
					xChk= eChk.offsetLeft;
				}
				/*
				*/
				
				//return [i,xChk,yChk-eTmp.offsetTop];
				pos[1]=i;
				pos[2]=xChk-eTmp.offsetLeft;
				pos[3]=yChk-eTmp.offsetTop;
			},
			
			//return the mouse event position
			//return 0 if fail or out of the text
			//return 1 if normal
			//return 2 if position on caret
			//pos: return position
			evtPos: function( evt, pos )
			{
				var eEvt= evt.srcElement?evt.srcElement:evt.target;
				
				//$ec.STATUS(eEvt.tagName);
				
				if( !this.isAncestor( $ec(this.eid), eEvt) && eEvt.id!=this.eid ) return 0;
				
				//get dx/dy
				var ei0= $ec(this.eidText).childNodes[0];
				var xy= this.getAbsXY( ei0 );
				var dx= evt.clientX-xy[0];
				var dy= evt.clientY-xy[1];
				
				//y
				var i= this.lineIndex(dy);
				//$ec.STATUS(i+","+dy);
				
				if(i==-1) return 0;
				pos[0]= i;
				
				//x
				var eLine= this._line(i);
				dy-=(eLine.offsetTop-ei0.offsetTop);
				this.colIndex( eLine, dx, dy, pos );
				
				return ( eEvt.id==this.eidCaret )?2:1;
			},
			
			JumpLine: function(nLine)
			{
				this.OnFocus();
				
				if( nLine<0 ) return;
				var ei=$ec(this.eidText);
				if( nLine>=ei.childNodes.length ) return;
				if( nLine>(this.GetLineNumber()-1) ) return;
				
				//alert(nLine);
				this.SetBlock(nLine,0,-1,0);
				/*
				this.pos[0]=nLine;
				this.pos[1]=0;
				this.pos2[0]=-1;
				this._updatePos(this.pos);
				this.HideBlock();
				*/
				this.UpdateCaret(1);
			},
			
			OnJumpLine: function()
			{
				var s=window.prompt("Jump to line:",this.pos[0]+1 );
				if(s)
				{
					s=parseInt(s);
					//if((s-1)==this.pos[0]) return;
					//window.setTimeout( "$ec.bind['"+this.eid+"'].JumpLine("+( s-1 )+")", 100 );
					this.JumpLine(s-1);
				}
			},
			
			//////////////////
			//focus function group
			//////////////////
			
			OnFocus: function(evt)
			{
				if(!this.bFocus)
				{
					//alert("OnFocus");
					
					var e=$ec(this.eid);
					e.style.borderColor="black";
					this.bFocus=true;
					
					//$ec.TRACE("OnFocus 1");
					//show input cursor
					this.UpdateCaret(1);
					//$ec.TRACE("OnFocus 2");
					
					$ec(this.eidBlock1).style.backgroundColor="lightblue";
					$ec(this.eidBlock2).style.backgroundColor="lightblue";
					$ec(this.eidBlock3).style.backgroundColor="lightblue";
					
					//attach global event
					var func= this.funcGlobalMouseDown;
					if( !func )
					{
						func= this.funcGlobalMouseDown= $ec.evtFunc(this.eid,"OnGlobalMousedown");
						
						if(document.attachEvent) document.attachEvent( "onmousedown", func );
						else document.addEventListener( "mousedown", func, false );
					}
					
					this.updateBackground();
					
					//$ec.TRACE("OnFocus 3");
				}
			},
			
			OnBlur: function(evt)
			{
				if(this.bFocus)
				{
					//alert("OnBlur");
					
					var e=$ec(this.eid);
					e.style.borderColor="lightgrey";
					this.bFocus=false;
					
					//hide input cursor
					this.HideCaret();
					$ec(this.eidBlock1).style.backgroundColor="lavender";
					$ec(this.eidBlock2).style.backgroundColor="lavender";
					$ec(this.eidBlock3).style.backgroundColor="lavender";
					
					/*
					//detach global event
					var func= this.funcGlobalMouseDown;
					if(func)
					{
						if(document.detachEvent) document.detachEvent( "onmousedown", func );
						else document.removeEventListener( "mousedown", func, false );
					}
					*/
					
					this.updateBackground();
					
					if( this.sEditing ) this.ClearInput();
					
					if( this.eidFind ) this.OnFindCloseClick();	//for SyntaxEditorEx
				}
			},
			
			//to make blur
			OnGlobalMousedown: function(evt)
			{
				//return;
				
				//$ec.TRACE("OnGlobalMousedown");
				if( this.bFocus )
				{
					//$ec.TRACE("OnGlobalMousedown 1");
					var eEvt= evt.srcElement?evt.srcElement:evt.target;
					if( eEvt.id!=this.eid && eEvt.id!=this.eidBind && !this.isAncestor($ec(this.eidBind), eEvt ) )
					{
						//window.status=(new Date).getTime()+" blur";
						this.OnBlur(evt);
					}
					//$ec.TRACE("OnGlobalMousedown 2");
				}
				//$ec.TRACE("OnGlobalMousedown end");
				
			},
			
			//////////////////
			//caret function group
			//////////////////
			
			oCaretX: {
				"ie": -1,
				"firefox": 0,
				"opera": -2,
				"safari": -1,
				"chrome": -1,
				"[unknown]": 0
				},
			
			oCaretY: {
				"ie": -1,
				"firefox": -2,
				"opera": -2,
				"safari": -2,
				"chrome": -2,
				"[unknown]": -1
				},
			
			//UpdateCaret: function([bFocus [, pos] ])
			UpdateCaret: function(bFocus,pos)
			{
				//$ec.TRACE("UpdateCaret");
				
				var ei=$ec(this.eidCaret);
				if( this.sEditing )
				{
					this.ClearInput();
				}
				
				
				/*
				ie6 bug: the offsetTop in OL/LI is wrong
				even if OL is with padding-top=0 and margin-top=0, its offsetTop is always 1-line-height,
				so caculate the line height in relative way;
				for offsetLeft, there's not error, and cause padding-top/margin-top is not 0, add value directly.
				*/
				//$ec.TRACE("UpdateCaret1a");
				
				if(!pos)
				{
					pos= (this.pos2[0]<0)?this.pos:this.pos2;
					
					if(this.eidPos) $ec(this.eidPos).innerHTML= (pos[0]+1)+","+(pos[1]+1);
				}
				
				var eLine= this._line(pos[0]);
				
				var ep= eLine.parentNode.parentNode;
				
				//$ec.TRACE("UpdateCaret1b");
				//var ntest= eLine.offsetTop;
				
				//$ec.TRACE("UpdateCaret1b-");
				
				if(!this.nLineHeight ) this.updateFontUnit();
				
				ei.style.left= (ep.offsetLeft+eLine.offsetLeft+pos[2]+this.oCaretX[this.sAgent]+(this.bDebug?0:1))+"px";
				//ei.style.top= (ep.offsetTop+eLine.offsetTop-eLine.parentNode.offsetTop+pos[3]+this.oCaretY[this.sAgent]+(this.bDebug?0:1))+"px";
				ei.style.top= (0+eLine.offsetTop+pos[3]+this.oCaretY[this.sAgent]+(this.bDebug?0:1))+"px";
				
				//if( this.bFocus &&  (( new Date).getTime()-$ec._tmLast)>300 ) $ec.THROW(( new Date).getTime() + " " +$ec._tmLast+" " +(( new Date).getTime()-$ec._tmLast)+ " too long");
				
				//$ec.TRACE("UpdateCaret1c");
				
				if( this.bOverwrite && this.pos2[0]==-1 ) this.showOverwriteBlock(eLine,pos);
				
				//$ec.TRACE("UpdateCaret2");
				if( bFocus )
				{
					/*
					ei.focus();
					ei.select();
					*/
					try
					{
						ei.focus();
						ei.select();
					}
					catch(e)
					{
						//return;
					}
					
					if( this.sAgent!="ie" && this.sAgent!="firefox" )
					{
						//$ec.STATUS("update caret");
						
						var eThis=$ec(this.eid);
						
						if( eThis.scrollTop>ei.offsetTop )
						{
							eThis.scrollTop-= (eThis.scrollTop-ei.offsetTop);
						}
						else if((eThis.scrollTop+eThis.offsetHeight-this.nLineHeight*3/2)<(ei.offsetTop+ei.offsetHeight))
						{
							eThis.scrollTop+= (ei.offsetTop+ei.offsetHeight)-(eThis.scrollTop+eThis.offsetHeight-this.nLineHeight*3/2);
						}
						
						if( eThis.scrollLeft>ei.offsetLeft )
						{
							eThis.scrollLeft-= (eThis.scrollLeft-ei.offsetLeft);
						}
						else if((eThis.scrollLeft+eThis.offsetWidth-this.nLineHeight*3/2)<(ei.offsetLeft+ei.offsetWidth))	//nLineHeight: for scrollbar width
						{
							eThis.scrollLeft+= (ei.offsetLeft+ei.offsetWidth)-(eThis.scrollLeft+eThis.offsetWidth-this.nLineHeight*3/2);
						}
					}
				}
				//$ec.TRACE("UpdateCaret3");
			},
			
			HideCaret: function()
			{
				this.HideSelect();
			},
			
			//////////////////
			//block function group
			//////////////////
			
			showOverwriteBlock: function(eLine,pos)
			{
				if(!pos) pos= (this.pos2[0]<0)?this.pos:this.pos2;
				if(!eLine) eLine= this._line(pos[0]);
				
				var ep= eLine.parentNode.parentNode;
				this.showBlock(
					1,
					ep.offsetLeft+eLine.offsetLeft+pos[2],
					ep.offsetTop+eLine.offsetTop-eLine.parentNode.offsetTop+pos[3],
					this.nCharWidth,
					this.nLineHeight,
					1
					);
			},
			
			//HideBlock: function([n])
			//n= 1,2,3 or other for all
			HideBlock: function(n)
			{
				if(n==1)
				{
					$ec(this.eidBlock1).style.display="none";
				}
				else if(n==2)
				{
					$ec(this.eidBlock2).style.display="none";
				}
				else if(n==3)
				{
					$ec(this.eidBlock3).style.display="none";
				}
				else
				{
					if( this.bOverwrite && this.pos2[0]==-1 && !this.bMousedown )
					{
						this.showOverwriteBlock();
					}
					else
					{
						$ec(this.eidBlock1).style.display="none";
					}
					$ec(this.eidBlock2).style.display="none";
					$ec(this.eidBlock3).style.display="none";
				}
			},
			
			//showBlock: function(n,l,t,w,h [,bOverwrite])
			//bOverwrite: only for n=1
			showBlock: function(n,l,t,w,h,bOverwrite)
			{
				var eb;
				if( n==1 ) eb=$ec(this.eidBlock1);
				else if( n==2 ) eb=$ec(this.eidBlock2);
				else if( n==3 ) eb=$ec(this.eidBlock3);
				else return;
				
				//window.status=n+","+l+","+t+","+w+","+h;
				
				eb.style.left=l+"px";
				eb.style.top=t+"px";
				eb.style.width=w+"px";
				eb.style.height=h+"px";
				eb.style.display="block";
				
				if( n==1 && this.bOverwrite ) eb.style.borderBottom= bOverwrite?"1px solid gray":"none";
			},
			
			//update [2]/[3] in pos
			updatePos: function(pos)
			{
				//$ec.TRACE("33");
				var eTmp= this.getTmpLine();
				
				/*
				ie bug??? if text is input and call from InsertString(),
				the '.offsetLeft' part in 'eTmp.childNodes[1].offsetLeft' will span 250ms; in other cases, it's normal.
				*/
				
				if( window.ActiveXObject && !this.nReadonly)
				{
					var r= document.body.createTextRange();
					r.moveToElementText(eTmp.parentNode.childNodes[pos[0]]);
					
					var i=r.boundingLeft;
					var j=r.boundingTop;
					//r.collapse(true);
					if( pos[1] ) r.moveStart( "character", pos[1] );
					
					pos[2]=r.boundingLeft-i;
					pos[3]=this.bWrap?(r.boundingTop-j):0;		//ie6 bug??: when call from drapData, and !Wrap, pos[3] will be LineHeight
					/*
					var r= document.body.createTextRange();
					r.moveToElementText(eTmp.parentNode.childNodes[pos[0]]);
					
					var r2= r.duplicate();
					r2.collapse(true);
					r2.moveEnd( "character", 1 );
					var i=r2.boundingLeft;
					var j=r2.boundingTop;
					
					//r.collapse(true);
					if( pos[1] ) r.moveStart( "character", pos[1] );
					
					pos[2]=r.boundingLeft-i;
					pos[3]=r.boundingTop-j;
					*/
				}
				else
				{
					if( this.bTextContent) eTmp.childNodes[0].textContent=eTmp.parentNode.childNodes[pos[0]].textContent.substring(0,pos[1]);
					else eTmp.childNodes[0].innerText=eTmp.parentNode.childNodes[pos[0]].innerText.substring(0,pos[1]);
					
					pos[2]= eTmp.childNodes[1].offsetLeft-eTmp.offsetLeft;
					pos[3]= eTmp.childNodes[1].offsetTop-eTmp.offsetTop;
				}
			},
			
			//SetBlock: function( p_0, p_1, p2_0, p2_1 )
			//SetBlock: function( p_0, p_1, -1 )
			SetBlock: function( p_0, p_1, p2_0, p2_1 )
			{
				this.pos[0]=p_0;
				this.pos[1]=p_1;
				
				//$ec.TRACE("31");
				this.updatePos(this.pos);
				//$ec.TRACE("32");
				
				this.pos2[0]=p2_0;
				if(p2_0!=-1)
				{
					this.pos2[1]=p2_1;
					this.updatePos(this.pos2);
					this.ShowBlock();
				}
				else this.HideBlock();
			},
			
			ShowBlock: function()
			{
				if( this.pos2[0]==-1 )
				{
					this.HideBlock();
					return;
				}
				if( this.pos[0]==this.pos2[0] && this.pos[1]==this.pos2[1] )
				{
					this.pos2[0]=-1;
					this.HideBlock();
					return;
				}
				
				var eText=$ec(this.eidText);
				var eLine,eLine2,pos,pos2,ep,ep2,top,top2;
				if( this.pos[0]<this.pos2[0] || ( this.pos[0]==this.pos2[0] && this.pos[1]<this.pos2[1] ) )
				{
					pos=this.pos; pos2=this.pos2;
				}
				else
				{
					pos=this.pos2; pos2=this.pos;
				}
				
				/*
				ie6 bug, refer UpdateCaret()
				*/

				//if( pos[0]==pos2[0] )
				if( pos[0]==pos2[0] && (!this.bWrap || pos[3]==pos2[3]) )
				{
					eLine= eText.childNodes[pos[0]];
					
					ep= eLine.parentNode.parentNode;
					
					this.showBlock(
						1,
						ep.offsetLeft+eLine.offsetLeft+pos[2],
						ep.offsetTop+eLine.offsetTop-eLine.parentNode.offsetTop+pos[3],
						pos2[2]-pos[2],
						this.nLineHeight
						);
					this.HideBlock(2);
					this.HideBlock(3);
				}
				else
				{
					eLine= eText.childNodes[pos[0]];
					eLine2= eText.childNodes[pos2[0]];
					
					ep= eLine.parentNode.parentNode;
					ep2= eLine2.parentNode.parentNode;
					
					top=ep.offsetTop+eLine.offsetTop-eLine.parentNode.offsetTop+pos[3];
					
					this.showBlock(
						1,
						ep.offsetLeft+eLine.offsetLeft+pos[2],
						top,
						eLine.parentNode.offsetWidth-pos[2],
						this.nLineHeight
						);
					
					top+=this.nLineHeight;
					top2=ep2.offsetTop+eLine2.offsetTop-eLine2.parentNode.offsetTop+pos2[3];
					
					if( (top2-top)<this.nLineHeight/2) this.HideBlock(2);
					else
					{
						this.showBlock(
							2,
							eLine.offsetLeft,
							top,
							//window.ActiveXObject?eLine.parentNode.offsetWidth:eLine.parentNode.offsetWidth,
							eLine.parentNode.offsetWidth,
							//eLine.parentNode.parentNode.parentNode.scrollWidth,
							top2-top
							);
					}
					
					this.showBlock(
						3,
						eLine.offsetLeft,
						top2,
						pos2[2],
						this.nLineHeight
						);
				}
			},
			
			copyPos: function( posSrc, posDest )
			{
				posDest[0]=posSrc[0];
				posDest[1]=posSrc[1];
				posDest[2]=posSrc[2];
				posDest[3]=posSrc[3];
			},
			
			//check if in block
			//return true/false; if no block, return false; if in edge, return true;
			isPosIn: function(pos)
			{
				if(pos[0]==-1 || this.pos2[0]==-1 ) return false;
				
				if( this.pos[0]<this.pos2[0] || (this.pos[0]==this.pos2[0] && this.pos[1]<=this.pos2[1]) )
				{
					if( pos[0]<this.pos[0] || (pos[0]==this.pos[0] && pos[1]<this.pos[1]) ||
						pos[0]>this.pos2[0] || (pos[0]==this.pos2[0] && pos[1]>this.pos2[1]) )
						return false;
				}
				else
				{
					if( pos[0]<this.pos2[0] || (pos[0]==this.pos2[0] && pos[1]<this.pos2[1]) ||
						pos[0]>this.pos[0] || (pos[0]==this.pos[0] && pos[1]>this.pos[1]) )
						return false;
				}
				
				return true;
			},
			
			//collapseBlock: function([bEnd])
			//collapse block by visible sequence
			collapseBlock: function(bEnd)
			{
				if( this.pos2[0]==-1 ) return;
				if( this.pos[0]<this.pos2[0] || ( this.pos[0]==this.pos2[0] && this.pos[1]<this.pos2[1] ) )
				{
					if( bEnd ) this.copyPos( this.pos2, this.pos );
				}
				else
				{
					if(!bEnd) this.copyPos( this.pos2, this.pos );
				}
				this.pos2[0]=-1;
				this.ShowBlock();
				this.UpdateCaret(this.bFocus);
			},
			
			resetBlock: function()
			{
				this.pos[0]=this.pos[1]=this.pos[2]=this.pos[3]=0;
				this.pos2[0]=-1;
				this.UpdateCaret(this.bFocus);
				this.ShowBlock();
			},
			
			//return visible block head
			getBlockHead: function()
			{
				return ( this.pos2[0]==-1 || this.pos[0]<this.pos2[0] || ( this.pos[0]==this.pos2[0] && this.pos[1]<this.pos2[1] ) )?this.pos:this.pos2;
			},
			
			//return a
			//a: buffer to receive
			GetBlockString: function(a)
			{
				if( this.pos2[0]==-1 )
				{
					a[a.length]="";
					return a;
				}
				
				//prepare
				var pos,pos2;
				if( this.pos[0]<this.pos2[0] || ( this.pos[0]==this.pos2[0] && this.pos[1]<this.pos2[1] ) )
				{
					pos=this.pos; pos2=this.pos2;
				}
				else
				{
					pos=this.pos2; pos2=this.pos;
				}
				
				var ei=$ec(this.eidText);
				var e1=ei.childNodes[pos[0]];
				
				//single line
				if( pos[0]==pos2[0] )
				{
					a[a.length]= this.getTextContent( e1 ).substring(pos[1],pos2[1]);
					return a;
				}
				
				//first
				a[a.length]= this.getTextContent( e1 ).slice(pos[1]);
				
				//middle
				var e2=ei.childNodes[pos2[0]];
				//var i=0;
				if(this.bTextContent )
				{
					while( (e1=e1.nextSibling)!=e2) { a[a.length]=e1.textContent.replace( /\r/g, "" ).replace( /\s/g, " " ); }
				}
				else
				{
					while( (e1=e1.nextSibling)!=e2) { a[a.length]=e1.innerText; }
				}
				
				//last
				a[a.length]= this.getTextContent(e2).substring(0,pos2[1]);
				
				return a;
			},
			
			SelectAll: function()
			{
			 	 var ei=$ec(this.eidText);
			 	 var i=ei.childNodes.length-1;
			 	 
			 	 ei=ei.lastChild;
			 	 if(ei.id==this.eidTmp)
			 	 {
			 	 	ei=ei.previousSibling;
			 	 	i--;
			 	 }
			 	 
			 	 this.OnFocus();
			 	 
			 	 //$ec.TRACE( i+" "+this.getTextContent(ei).length );
			 	 //aaa
			 	 this.SetBlock( i, this.getTextContent(ei).length, 0, 0 );
			 	 //$ec.TRACE( this.pos+" "+this.pos2 );
			 	 
			 	 //this.ShowBlock();
			 	 //this.UpdateCaret();
			},
			
			//////////////////
			//system selection function group
			//////////////////
			
			HideSelect: function()
			{
				if(window.getSelection) window.getSelection().removeAllRanges();
				else if( document.selection && document.selection.empty ) document.selection.empty();
			},
			
			// return [obj,idx], if idx=-1, the end of obj
			getRangeDetail_mz: function( obj, idx )
			{
				var i,imax,len;
				imax=obj.childNodes.length;
				for( i=0;i<imax;i++)
				{
					len=obj.childNodes[i].textContent.length;
					if( len>idx )
					{
						obj=obj.childNodes[i];
						if( obj.nodeType==3 ) return [obj,idx]; //text
						else return this.getRangeDetail_mz( obj, idx );
					}
					else if( len==idx )
					{
						return [obj.childNodes[i],-1];
					}
					else idx-=len;
				}
				return [obj,-1];
			},
			
			ShowSelect: function()
			{
				//$ec.TRACE("ShowSelect");
				
				if( this.pos2[0]==-1 )
				{
					this.HideSelect();
					return;
				}
				
				if( this.pos[0]==this.pos2[0] && this.pos[1]==this.pos2[1] )
				{
					this.HideSelect();
					return;
				}
				
				var eText=$ec(this.eidText);
				var eLine,eLine2,pos,pos2;
				if( this.pos[0]<this.pos2[0] || ( this.pos[0]==this.pos2[0] && this.pos[1]<this.pos2[1] ) )
				{
					pos=this.pos; pos2=this.pos2;
				}
				else
				{
					pos=this.pos2; pos2=this.pos;
				}
				
				if( pos[0]==pos2[0] )
				{
					eLine= eText.childNodes[pos[0]];
					if(window.ActiveXObject)
					{
						var rng = document.body.createTextRange();
						rng.moveToElementText(eLine);
						rng.collapse(true);
						rng.moveEnd( "character", pos2[1] );
						if( pos[1]>0 ) rng.moveStart( "character", pos[1] );
						rng.select();
					}
					else
					{
						var r= document.createRange();
						
						var d= this.getRangeDetail_mz(eLine,pos[1]);
						if( d[1]!=-1 ) r.setStart( d[0],d[1]);
						else r.setStartAfter( d[0] );
						
						d= this.getRangeDetail_mz(eLine,pos2[1]);
						if( d[1]!=-1 ) r.setEnd( d[0],d[1]);
						else r.setEndAfter( d[0] );
						
						window.getSelection().removeAllRanges();
						window.getSelection().addRange(r);
					}
				}
				else
				{
					eLine= eText.childNodes[pos[0]];
					eLine2= eText.childNodes[pos2[0]];
					
					if(window.ActiveXObject)
					{
						var rng = document.body.createTextRange();
						rng.moveToElementText(eLine);
						rng.moveStart( "character", pos[1] );
						
						var rng2 = document.body.createTextRange();
						rng2.moveToElementText(eLine2);
						rng2.collapse(true);
						rng2.moveEnd( "character", pos2[1] );
						
						rng.setEndPoint("EndToEnd",rng2);
						
						rng.select();
					}
					else
					{
						var r= document.createRange();
						
						var d= this.getRangeDetail_mz(eLine,pos[1]);
						if( d[1]!=-1 ) r.setStart( d[0],d[1]);
						else r.setStartAfter( d[0] );
						
						d= this.getRangeDetail_mz(eLine2,pos2[1]);
						if( d[1]!=-1 ) r.setEnd( d[0],d[1]);
						else r.setEndAfter( d[0] );
						
						window.getSelection().removeAllRanges();
						window.getSelection().addRange(r);
					}
				}
			},
			
			//////////////////
			//data operating function group
			//////////////////
			
			_op_fail: function(sErr)
			{
				//this.aOp.splice(0,this.aOp.length);
				this.aOp.splice(0,this.nOpIdx+1);
				this.nOpIdx=0;
				alert("op fail"+(sErr?(": "+sErr):""));
				throw "op fail";
			},
			
			//save operation
			//o: [sKey,nLine,nCol,sDel,sIns]
			_saveop: function(o)
			{
				//clear buffer end
				if(this.nOpIdx<this.aOp.length) this.aOp.splice(this.nOpIdx,this.aOp.length-this.nOpIdx);
				
				if( o[0] && this.nOpIdx>0 )
				{
					//try to combine op
					var a= this.aOp[this.nOpIdx-1];
					
					if( a[0]==o[0] )
					{
						switch(o[0])
						{
							case "Del":
								if( a[1]==o[1] && a[2]==o[2] && o[3].length==1 && !o[4] )
								{
									a[3]+=o[3];
									return;
								}
								break;
							case "BS":
								if( a[1]==o[1] && a[2]==(o[2]+1) && o[3].length==1 && !o[4] )
								{
									a[3]=o[3]+a[3];
									a[2]-=1;
									return;
								}
								break;
							case "Inp":
								if( a[1]==o[1] && (a[2]+a[4].length)==o[2] && ((a[3].length==0 && o[3].length==0)||(a[3].length>0 && o[3].length==1)) && a[4].length>0 && o[4].length>0 )
								{
									var ch1= a[4].charCodeAt(a[4].length-1);
									var ch2= o[4].charCodeAt(0);
									
									ch1=((47<ch1 && ch1<58) || (64<ch1 && ch1<91) || (96<ch1 && ch1<123))?1:0;
									ch2=((47<ch2 && ch2<58) || (64<ch2 && ch2<91) || (96<ch2 && ch2<123))?1:0;
									
									if( !(ch2==1 && ch1==0) )
									{
										a[3]+=o[3];
										a[4]+=o[4];
										return;
									}
								}
								break;
						}
					}
				}
				
				this.aOp[this.aOp.length]=o;
				this.nOpIdx++;
			},
			
			_comparestr: function(str1,str2)
			{
				if(window.ActiveXObject) return ( str1==str2 )?0:1;
				else return ( str1.replace(/\s/g," ")==str2.replace(/\s/g," ") )?0:1;
			},
			
			//return end [nLine,nCol], null if fail
			//nMode: 0/undefined:normal, 1:undo/redo/do_not_save, 2:passive changing, record step only
			_op: function(nLine,nCol,sDel,sIns,bRaw,nMode,sKey)
			{
				//alert(escape(sIns));
				//alert(bRaw);
				
				if( !sIns && !sDel ) return [nLine,nCol];   //void op
				
				if(nMode==2)
				{
					this._saveop([sKey,nLine,nCol,sDel,sIns]);
					return;
				}
				
				if( !this.bModified )
				{
					this.bModified= true;
					$ec.$notify( this, "before-modify", null );
				}
				
				var a,obj,obji,objp,s,s2,i,imax,j,jmax;
				
				obj=this._line(nLine);
				if(!obj){ this._op_fail("line fail"); return null; }
				s=this.getTextContent(obj);
				
				//delete
				if(sDel)
				{
					a=sDel.replace( /\r/g , "").split("\n");
					imax=a.length-1;
					if(imax>0)
					{
						//check
						//first
						if( this._comparestr( s.substring(nCol,s.length),a[0] )!=0 )
						{
							//alert( "["+s.substring(nCol,s.length)+"]\n["+a[0]+"]" );
							this._op_fail("not match(1)");
							return null;
						}
						
						//middle
						obji=obj;
						
						if(window.ActiveXObject)
						{
							for(i=1;i<imax;)
							{
								jmax=i+200; if(jmax>imax)jmax=imax;
								for(j=i;j<jmax;j++)
								{
									if( this.getTextContent(obji=obji.nextSibling)!=a[j] ) { this._op_fail("not match(3):"+j/*+"\n|"+a[j]+"|\n|"+this.getTextContent(obji)+"|"*/); return null; }
								}
								i=jmax; window.status="checking "+i+"/"+imax+ " ("+Math.round(i*100/imax)+"%)";
							}
						}
						else
						{
							for(i=1;i<imax;)
							{
								jmax=i+200; if(jmax>imax)jmax=imax;
								for(j=i;j<jmax;j++)
								{
									if( this.getTextContent(obji=obji.nextSibling).replace(/\s/g," ")!=a[j].replace(/\s/g," ") ) { this._op_fail("not match(4)"); return null; }
								}
								i=jmax; window.status="checking "+i+"/"+imax+ " ("+Math.round(i*100/imax)+"%)";
							}
						}
						//last
						s2=this.getTextContent(obji.nextSibling);
						if( this._comparestr( s2.substring(0,a[i].length),a[i] )!=0 ) { this._op_fail("not match(5)"); return null; }
						
						//delete
						s=s.substring(0,nCol)+s2.substring(a[i].length,s2.length);
						objp=obj.parentNode;
						imax++;
						for(i=1;i<imax;)
						{
							jmax=i+200; if(jmax>imax)jmax=imax;
							for(j=i;j<jmax;j++) objp.removeChild(obj.nextSibling);
							i=jmax; window.status="deleting "+i+"/"+imax+ " ("+Math.round(i*100/imax)+"%)";
						}
						window.status="";
						if( this.bTextContent ) obj.textContent=s;
						else obj.innerText=s;
					}
					else
					{
						//check
						if(this._comparestr( s.substring(nCol,nCol+a[0].length),a[0] )!=0 ) { this._op_fail("not match(7)"); return null; }

						//delete
						s=s.substring(0,nCol)+s.substring(nCol+a[0].length,s.length);
						if( this.bTextContent ) obj.textContent=s;
						else obj.innerText=s;
					}
					if(!s) obj.style.minHeight="1em";
				}
				
				//insert
				if(sIns)
				{
					if( bRaw )
					{
						var expandTab=this.expandTab;

						a=sIns.replace( /\r/g,"" ).replace( /\u00a0/," " ).split("\n");
						a[0]=expandTab(a[0],nCol);
						imax=a.length;
						for(i=1;i<imax;)
						{
							jmax=i+200; if(jmax>imax)jmax=imax;
							for(j=i;j<jmax;j++) a[j]=expandTab(a[j],0);
							i=jmax; window.status="normalizing "+i+"/"+imax+ " ("+Math.round(i*100/imax)+"%)";
						}
						window.status="";
					}
					else
					{
						a=sIns.split("\n");
					}
					
					imax=a.length-1;
					if(imax>0)
					{
						//first
						this.setTextContent(obj,s.substring(0,nCol)+a[0]);
				
						//middle
						objp=obj.parentNode;
						obji=obj.nextSibling;
						if( this.bTextContent )
						{
							if( obji)
							{
								for(i=1;i<imax;)
								{
									jmax=i+200; if(jmax>imax)jmax=imax;
									for(j=i;j<jmax;j++)
									{
										if(a[j]) objp.insertBefore( document.createElement("li"),obji ).textContent=a[j];
										else objp.insertBefore( document.createElement("li"),obji ).style.minHeight="1em";
									}
									i=jmax; window.status="inserting "+i+"/"+imax+ " ("+Math.round(i*100/imax)+"%)";
								}
							}
							else
							{
								for(i=1;i<imax;)
								{
									jmax=i+200; if(jmax>imax)jmax=imax;
									for(j=i;j<jmax;j++)
									{
										if(a[j]) objp.appendChild( document.createElement("li") ).textContent=a[j];
										else objp.appendChild( document.createElement("li") ).style.minHeight="1em";
									}
									i=jmax; window.status="inserting "+i+"/"+imax+ " ("+Math.round(i*100/imax)+"%)";
								}
							}
						}
						else
						{
							if( obji)
							{
								for(i=1;i<imax;)
								{
									jmax=i+200; if(jmax>imax)jmax=imax;
									for(j=i;j<jmax;j++)
									{
										if(a[j]) objp.insertBefore( document.createElement("li"),obji ).innerText=a[j];
										else objp.insertBefore( document.createElement("li"),obji ).style.minHeight="1em";
									}
									i=jmax; window.status="inserting "+i+"/"+imax+ " ("+Math.round(i*100/imax)+"%)";
								}
							}
							else
							{
								for(i=1;i<imax;)
								{
									jmax=i+200; if(jmax>imax)jmax=imax;
									for(j=i;j<jmax;j++)
									{
										if(a[j]) objp.appendChild( document.createElement("li") ).innerText=a[j];
										else objp.appendChild( document.createElement("li") ).style.minHeight="1em";
									}
									i=jmax; window.status="inserting "+i+"/"+imax+ " ("+Math.round(i*100/imax)+"%)";
								}
							}
						}
						window.status="";
						//last
						s=a[i]+s.substring( nCol, s.length );
						if( obji) this.setTextContent( obj=objp.insertBefore( document.createElement("li"),obji ),s );
						else this.setTextContent( obj=objp.appendChild( document.createElement("li") ), s );
						
						if(!s) obj.style.minHeight="1em";
						
						if( !nMode ) this._saveop([sKey,nLine,nCol,sDel,bRaw?a.join("\n"):sIns]);
						this.updateLineNumber();
						
						return [nLine+imax,a[i].length];
					}
					else
					{
						s=s.substring(0,nCol)+a[0]+s.substring(nCol,s.length);
						if( this.bTextContent ) obj.textContent=s;
						else obj.innerText=s;
						
						if(!s) obj.style.minHeight="1em";
						
						if( !nMode ) this._saveop([sKey,nLine,nCol,sDel,bRaw?a.join("\n"):sIns]);
						this.updateLineNumber();
				
						//alert(a.length);
						//return null;
						
						return [nLine,nCol+a[0].length];
					}
				}
				else
				{
					if( !nMode ) this._saveop([sKey,nLine,nCol,sDel,""]);
					return [nLine,nCol];
				}
			},
			
			Undo: function()
			{
				if( this.nReadonly ) return;
				
				if(this.nOpIdx<1 ) return;
				//alert( this.nOpIdx+"\n"+"["+this.aOp.join("]\n[")+"]" );

				this.OnFocus();
				
				this.nOpIdx-=1;
				
				//alert(this.nOpIdx);
				var a=this.aOp[this.nOpIdx];
				if(a[0] && a[0].charAt(0)!="_" )a[0]="";	//stop continous op
				
				//alert(a);
				
				var re,aChk,eLine;
				if( a[0]=="_end" )
				{
					while(1)
					{
						if(this.nOpIdx<1 ) return;
						this.nOpIdx-=1;
						aChk=this.aOp[this.nOpIdx];
						if(aChk[0]=="_head") break;
						else if(aChk[0]=="_end") { this._op_fail("group fail"); return; }
						
						a=aChk;
						re= this._op( a[1], a[2], a[4], a[3], 0, 1 );
						if(re)
						{
							/*
							obj=this.objTop.childNodes[a[1]];
							obj.setAttribute("Stack","");
							this.UpdateLine( obj );
							*/
							eLine= this._line(a[1]);
							eLine.setAttribute("Stack","");
							this.FormatLine(a[1]);
						}
					}
				}
				else if(a[0]=="_head") { this._op_fail("group fail"); return; }
				else
				{
					re= this._op( a[1], a[2], a[4], a[3], 0, 1 );
					if(re)
					{
						/*
						obj=this.objTop.childNodes[a[1]];
						obj.setAttribute("Stack","");
						this.UpdateLine( obj );
						*/
						eLine= this._line(a[1]);
						eLine.setAttribute("Stack","");
						this.FormatLine(a[1]);
					}
				}
				
				if(re)
				{
			//		this.UpdateLine( this.objTop.childNodes[re[0]] );
			//		var obj=this.objTop.childNodes[a[1]];
			//		obj.setAttribute("Stack","");
			//		this.UpdateLine( obj );
			
					//this.SetCaret( this.objTop.childNodes[re[0]], re[1] );
					
					this.SetBlock(re[0],re[1],-1);
					this.UpdateCaret(1);
				}
			},
			
			Redo: function()
			{
				if( this.nReadonly ) return;
				
				if(this.nOpIdx>=this.aOp.length ) return;
				this.nOpIdx+=1;
				
				this.OnFocus();
				
				var a=this.aOp[this.nOpIdx-1];
				if(a[0] && a[0].charAt(0)!="_" )a[0]="";	//stop continous op
				
				//this.ClearSelection_mz();
				
				var re,aChk,eLine;
				if( a[0]=="_head" )
				{
					while(1)
					{
						if(this.nOpIdx>=this.aOp.length ) return;
						this.nOpIdx+=1;
						aChk=this.aOp[this.nOpIdx-1];
						if(aChk[0]=="_end") break;
						else if(aChk[0]=="_head") { this._op_fail("group fail"); return; }
						
						a=aChk;
						re= this._op( a[1], a[2], a[3], a[4], 0, 1 );
						//if(!obj1) obj1= this.objTop.childNodes[a[1]];
						//this.objTop.childNodes[a[1]].setAttribute("Stack","");
						if(re)
						{
							/*
							obj=this.objTop.childNodes[a[1]];
							obj.setAttribute("Stack","");
							this.UpdateLine( obj );
							*/
							eLine= this._line(a[1]);
							eLine.setAttribute("Stack","");
							this.FormatLine(a[1]);
						}
					}
				}
				else if(a[0]=="_end") { this._op_fail("group fail"); return; }
				else
				{
					re= this._op( a[1], a[2], a[3], a[4], 0, 1 );
					if(re)
					{
						/*
						obj=this.objTop.childNodes[a[1]];
						obj.setAttribute("Stack","");
						this.UpdateLine( obj );
						*/
						eLine= this._line(a[1]);
						eLine.setAttribute("Stack","");
						this.FormatLine(a[1]);
					}
				}
				
				if(re)
				{
			//		this.UpdateLine( this.objTop.childNodes[re[0]] );
			//		var obj=obj1?obj1:this.objTop.childNodes[a[1]];
			//		obj.setAttribute("Stack","");
			//		this.UpdateLine( obj );
					//this.SetCaret( this.objTop.childNodes[re[0]], re[1] );
					
					this.SetBlock(re[0],re[1],-1);
					this.UpdateCaret(1);
				}
			},
			
			//////////////////
			//keyboard function group
			//////////////////
			
			CancelOperate: function()
			{
				//$ec.TRACE("CancelOperate");
				if( this.bMousedown)
				{
					if( this.nMouseMode==2 )
					{
						this.nMouseMode= -1;
						$ec(this.eidText).style.cursor="auto";
						this.UpdateCaret();
						$ec(this.eidCaret).select();
					}
				}
			},
			
			/*
			Up/Down/Left/Right/PageUp/PageDown:	move caret
			Ctrl+Left/Right: move to group of word-chars, not-word-chars, spaces, unicode-chars
			Ctrl+Up/Down: move and scroll 1 line
			
			Home: move to line head
			Ctrl+Home: move to the head of all text
			End: move to line end
			Ctrl+End: move to the end of all text
			
			Shift (+Ctrl) +Up/Down/Left/Right/PageUp/PageDown/Home/End/Click: block selection
			
			Backspace: backspace char ( if caret in head spaces of a line, backspace a Tab/4 spaces )
			Ctrl+Backspace: backspace group of word-chars, not-word-chars, spaces, unicode-chars
			Del: delete 1 char ( hint: to delete Tab, use backspace )
			Ctrl+Del: delete group of word-chars, not-word-chars, spaces, unicode-chars
			
			Tab (Caret): insert tab ( 4 spaces )
			Shift+Tab (Caret): move caret to previous tab, in same line.
			Tab (Block): insert block tab at head
			Shift+Tab (Block): remove block tab at head
			
			Ctrl+C:	copy
			Ctrl+X: cut
			Ctrl+V: paste
			
			Ctrl+A: select all
			Ctrl+G: jump to line
			
			Ctrl+F: find string
			F3: find next
			Shift+F3: find previous
			
			Ctrl+Z: undo
			Ctrl+Y: redo
			
			Ctrl+F2: toggle bookmark
			F2: goto next bookmark
			Shift+F2: goto previous bookmark
			
			F1: show simple help
			Ctrl+F1: show about information
			
			Insert: toggle overwrite mode
			
			Drag/Drop: locally drag and drop in current editor
			Ctrl+Drag/Drop: locally drag and copy in current editor
			
			Esc: cancel running operation - Drag/Drop
			
			*/
			OnKeydown: function(evt)
			{
				this.bPreventKey=false;
				var ei,s,mr,n,pos,pos2,i,j,r;
				
				if( this.nVertCol && evt.keyCode!=38 && evt.keyCode!=40 && evt.keyCode!=33 && evt.keyCode!=34 )	//up/down/page up/page down
				{
					this.nVertCol=0;
				}
				//alert("a");
				
				if( evt.keyCode==13 )	//enter
				{
					s= this._lineText().replace( this.regGetHeadSpace , "$1" );
					j=(s.length>this.pos[1])?this.pos[1]:s.length;
					i=j-j%4;
					this.InsertString("\n"+s.substring(0,i));
					
					//remove space after caret
					s= this._lineText().replace( this.regGetHeadSpace , "$1" );
					if(s.length>this.pos[1])
					{
						this.pos2[0]=this.pos[0];
						this.pos2[1]=s.length;
						this.DeleteBlock("");
					}
					
					$ec(this.eidCaret).focus();	//view will not auto scroll if donn't focus
					
					//some browser cann't prevent enter key
					/*
					if( this.sAgent=="opera" )
					{
						this.LockCaret();
					}
					*/
					
					this.LockCaret();
					this.sKeyCmd="enter";
					
					this.bPreventKey=true;
				}
				else if( evt.keyCode==8 )	//backspace
				{
					if( this.pos2[0]!=-1) this.DeleteBlock("BS");	//selection block
					else
					{
						//check if deleting head space
						if( this.pos[1]>0 )
						{
							s= this._lineText().replace( this.regGetHeadSpace , "$1" );
							if( this.pos[1]<=s.length )
							{
								i=this.pos[1]%4;
								if(i==0)i=4;
								this.pos[1]=this.pos[1]-i;
								
								i=s.length%4;
								if(i==0)i=4;
								this.pos2[0]=this.pos[0];
								this.pos2[1]=this.pos[1]+i;
								
								this.DeleteBlock("BS");
							}
							else
							{
								this.nextPostionX( this.pos, evt.ctrlKey?-2:-1, this.pos2 );
								this.DeleteBlock("BS");
							}
						}
						else
						{
							this.nextPostionX( this.pos, evt.ctrlKey?-2:-1, this.pos2 );
							this.DeleteBlock("BS");
						}
					}
				}
				else if( evt.keyCode==46 )	//del
				{
					if( this.pos2[0]!=-1) this.DeleteBlock("Del");	//selection block
					else
					{
						this.nextPostionX( this.pos, evt.ctrlKey?2:1, this.pos2 );
						this.DeleteBlock("Del");
					}
				}
				else if( evt.keyCode==38 || evt.keyCode==40 )	//up or down
				{
					if( this.pos2[0]!=-1 && !evt.shiftKey ) this.collapseBlock( (evt.keyCode==38)?0:1 );
					else
					{
						pos= (evt.shiftKey && this.pos2[0]!=-1)?this.pos2:this.pos;	//source
						
						if(!this.nVertCol && pos[1]>0 ) this.nVertCol= pos[1];
						
						n= this.nextPostionY(
							pos,
							(evt.keyCode==38)?-1:1,
							pos2= (evt.shiftKey?this.pos2:this.pos)		//dest
							);
						
						if( this.nVertCol>0 && pos2[1]<this.nVertCol) pos2[1]= (n>this.nVertCol)?this.nVertCol:n;
						
						//$ec.STATUS(n+","+this.nVertCol+","+pos2[1]);
						
						this.updatePos( pos2 );
						
						if( evt.shiftKey ) this.ShowBlock();
						//else this.pos2[0]=-1;
						
						this.UpdateCaret(1);
					}
					
					if( evt.ctrlKey )
					{
						//scroll 1 line
						ei= $ec(this.eidText).parentNode.parentNode;
						
						if(evt.keyCode==38) ei.scrollTop-=this.nLineHeight;
						else ei.scrollTop+=this.nLineHeight;
					}
					
					//$ec(this.eidCaret).focus();	//view will not auto scroll if donn't focus
				}
				else if( evt.keyCode==37 || evt.keyCode==39 )	//left or right
				{
					if( this.pos2[0]!=-1 && !evt.shiftKey ) this.collapseBlock( (evt.keyCode==37)?0:1 );
					else
					{
						this.nextPostionX(
							(evt.shiftKey && this.pos2[0]!=-1)?this.pos2:this.pos,
							evt.ctrlKey? ((evt.keyCode==37)?-2:2) : ((evt.keyCode==37)?-1:1),
							pos=(evt.shiftKey?this.pos2:this.pos)
							);
						this.updatePos(pos );
						
						if( evt.shiftKey ) this.ShowBlock();
						//else this.pos2[0]=-1;
						
						this.UpdateCaret(1);
					}
					
					//$ec(this.eidCaret).focus();	//view will auto scroll
				}
				else if( evt.keyCode==33 || evt.keyCode==34 )	//page up or page down
				{
					//scroll
					ei= $ec(this.eidText).parentNode.parentNode;
					ei.scrollTop+= (ei.offsetHeight-this.nLineHeight);
					
					//move caret
					pos= (evt.shiftKey && this.pos2[0]!=-1)?this.pos2:this.pos;	//source
					
					if(!this.nVertCol && pos[1]>0 ) this.nVertCol= pos[1];
					
					j= Math.floor(ei.offsetHeight/this.nLineHeight)-1;
					
					n= this.nextPostionY(
						pos,
						(evt.keyCode==33)?-j:j,
						pos2= (evt.shiftKey?this.pos2:this.pos)		//dest
						);
					
					if( this.nVertCol>0 && pos2[1]<this.nVertCol) pos2[1]= (n>this.nVertCol)?this.nVertCol:n;
					
					this.updatePos( pos2 );
					
					if( evt.shiftKey ) this.ShowBlock();
					//else this.pos2[0]=-1;
					
					this.UpdateCaret(1);
				}
				else if( evt.keyCode==36 )	//home
				{
					if( evt.ctrlKey )
					{
						if(evt.shiftKey)
						{
							this.pos2[0]=this.pos2[1]=0;
							this.updatePos(this.pos2);
							this.ShowBlock();
						}
						else
						{
							this.resetBlock();
						}
						this.UpdateCaret(1);		//focus for firefox
					}
					else
					{
						j=this._lineText().replace( this.regGetHeadSpace,"$1").length;
						if(evt.shiftKey)
						{
							if(this.pos2[0]==-1){ this.pos2[0]=this.pos[0]; this.pos2[1]=this.pos[1]; }
							this.pos2[1]= ( this.pos2[1]!=j )?j:0;
							this.updatePos(this.pos2);
							this.ShowBlock();
						}
						else
						{
							this.SetBlock( this.pos[0], ( this.pos[1]!=j )?j:0, -1 );
						}
						this.UpdateCaret();
					}
				}
				else if( evt.keyCode==35 )	//end
				{
					if( evt.ctrlKey )
					{
						j=this.GetLineNumber();
						s=this._lineText(j-1);
						if(evt.shiftKey)
						{
							this.pos2[0]=j-1;
							this.pos2[1]=s.length;
							this.updatePos(this.pos2);
							this.ShowBlock();
						}
						else
						{
							this.SetBlock(j-1,s.length,-1);
						}
						this.UpdateCaret(1);		//focus for firefox
					}
					else
					{
						j=this._lineText().length;
						if(evt.shiftKey)
						{
							if(this.pos2[0]==-1){ this.pos2[0]=this.pos[0];}
							this.pos2[1]=j;
							this.updatePos(this.pos2);
							this.ShowBlock();
						}
						else
						{
							this.SetBlock( this.pos[0], j, -1 );
						}
						this.UpdateCaret();
					}
				}
				else if( evt.keyCode==9 )	//tab
				{
					if( this.pos2[0]!=-1 )
					{
						//$ec.STATUS(evt.shiftKey);
						this.TabBlock(evt.shiftKey);
					}
					else
					{
						if( evt.shiftKey )
						{
							if( this.pos[1]>0 )
							{
								//move caret back
								j= this.pos[1]%4;
								this.pos[1]-=((j>0)?j:4);
								this.updatePos(this.pos );
								this.UpdateCaret(1);
							}
						}
						else
						{
							j=this._lineText().slice( this.pos[1] ).replace( this.regGetHeadSpace,"$1").length%4;
							if(j>0)
							{
								this.pos2[0]=this.pos[0];
								this.pos2[1]=this.pos[1]+j;
							}
							
							i=4-(this.pos[1]%4);
							//$ec.STATUS( j+","+i );
							this.InsertString(this.spa[i]);
						}
					}
					
					if(this.sAgent=="opera" )
					{
						//Opera will tab to other control
						window.setTimeout( "$ec.bind['"+this.eid+"'].UpdateCaret(1)", 0 );
						this.sKeyCmd="tab";
					}
					
					this.bPreventKey=true;
				}
				else if( evt.keyCode==67 )		// Ctrl+C
				{
					if( evt.ctrlKey )
					{
						if( this.sEditing ) this.ClearInput();
						
					 	if(this.pos2[0]!=-1)
					 	{
					 		this.LockCaret(this.GetBlockString([]).join("\n"));
							this.sKeyCmd="copy";
						}
					}
				}
				else if( evt.keyCode==88 )		// Ctrl+X
				{
					if( evt.ctrlKey )
					{
						if( this.sEditing ) this.ClearInput();
						
					 	if(this.pos2[0]!=-1)
					 	{
					 		this.LockCaret(this.GetBlockString([]).join("\n"));
					 		this.sKeyCmd="cut";
					 	}
					}
				}
				else if( evt.keyCode==86 )		// Ctrl+V
				{
					if( evt.ctrlKey )
					{
						if( this.sEditing ) this.ClearInput();
						
						if( window.ActiveXObject )
						{
							//for ie, to speed up
							
							this.bPreventKey=true;
							window.setTimeout( "$ec.bind['"+this.eid+"'].PasteData()", 0 );
						}
						else
						{
							this.LockCaret("");
							this.sKeyCmd="paste";
						}
					}
				}
				else if( evt.keyCode==65 )		// Ctrl+A
				{
					 if( evt.ctrlKey )
					 {
					 	 this.SelectAll();
					 }
				}
				else if( evt.keyCode==70 )		// Ctrl+F
				{
					if( evt.ctrlKey )
					{
						this.bPreventKey=true;
						window.setTimeout( "$ec.bind['"+this.eid+"'].OnFind()", 0 );
					}
				}
				else if( evt.keyCode==114 )	//F3
				{
					this.bPreventKey=true;
					window.setTimeout( "$ec.bind['"+this.eid+"'].FindNext("+(evt.shiftKey?1:"")+")", 0 );
				}
				else if( evt.keyCode==71 )		// Ctrl+G
				{
					if( evt.ctrlKey )
					{
						this.bPreventKey=true;
						window.setTimeout( "$ec.bind['"+this.eid+"'].OnJumpLine()", 0 );
					}
				}
				else if( evt.keyCode==90 )		// Ctrl+Z
				{
					if( evt.ctrlKey )
					{
						this.bPreventKey=true;
						this.sKeyCmd="undo";
					}
				}
				else if( evt.keyCode==89 )		// Ctrl+Y
				{
					if( evt.ctrlKey )
					{
						this.bPreventKey=true;
						this.sKeyCmd="redo";
					}
				}
				else if( evt.keyCode==113 )	//F2
				{
					if( evt.ctrlKey )
					{
						window.setTimeout("$ec.bind['"+this.eid+"'].ToggleBookmark()",0);
					}
					else if(evt.shiftKey)
					{
						window.setTimeout("$ec.bind['"+this.eid+"'].NextBookmark(1)",0);
					}
					else
					{
						window.setTimeout("$ec.bind['"+this.eid+"'].NextBookmark()",0);
					}
				}
				else if( evt.keyCode==112 )	//F1
				{
					this.bPreventKey=true;
					if( evt.ctrlKey )
					{
						window.setTimeout("$ec.bind['"+this.eid+"'].ShowAbout()",0);
					}
					else
					{
						window.setTimeout("$ec.bind['"+this.eid+"'].ShowHelp()",0);
					}
				}
				else if( evt.keyCode==27 )		// ESC
				{
					this.bPreventKey=true;
					window.setTimeout("$ec.bind['"+this.eid+"'].CancelOperate()",0);
				}
				else if( evt.keyCode==45 )		// Insert
				{
					this.bPreventKey=true;
					this.ToggleOverwrite();
				}
				
				window.setTimeout( "$ec.bind['"+this.eid+"'].CheckInput()", 0 );
				
				if( this.bPreventKey ) return this.OnPreventEvent(evt);
				
			},
			
			OnKeyup: function(evt)
			{
				if( this.sKeyCmd )
				{
					if(this.sKeyCmd =="copy")
					{
						//window.setTimeout("if($ec('"+this.eidCaret+"').style.display!='none')$ec('"+this.eidCaret+"').focus()",0);
						window.setTimeout("$ec.bind['"+this.eid+"'].UnlockCaret(1)",0);
					}
					else if(this.sKeyCmd =="cut")
					{
						this.DeleteBlock();
						window.setTimeout("$ec.bind['"+this.eid+"'].UnlockCaret()",0);
					}
					else if(this.sKeyCmd =="paste")
					{
						window.setTimeout("$ec.bind['"+this.eid+"'].UnlockCaret();$ec.bind['"+this.eid+"'].CheckInput();$ec.bind['"+this.eid+"'].ClearInput();",0);
					}
					else if( this.sKeyCmd =="undo" )
					{
						window.setTimeout( "$ec.bind['"+this.eid+"'].Undo()", 0 );
					}
					else if( this.sKeyCmd =="redo" )
					{
						window.setTimeout( "$ec.bind['"+this.eid+"'].Redo()", 0 );
					}
					else if( this.sKeyCmd =="tab" )
					{
						//$ec.STATUS("tab");
						window.setTimeout( "$ec.bind['"+this.eid+"'].UpdateCaret(1)", 0 );
					}
					else if(this.sKeyCmd =="enter")
					{
						window.setTimeout("$ec.bind['"+this.eid+"'].UnlockCaret(1);$ec.bind['"+this.eid+"'].ClearInput();",0);
					}
					
					this.sKeyCmd="";
				}
				
				//window.setTimeout( "$ec.bind['"+this.eid+"']._checkInput()", 0 );
				
				if( this.bPreventKey )
				{
					//pass 1 time
					this.bPreventKey= false;
					
					return this.OnPreventEvent(evt);
				}
			},
			
			//nextPostionX: function( pos, dx, posNext )
			//pos: source
			//dx: 1/-1, 2/-2:group chars of word/not-word/space/unicode
			//posNext: dest/output
			nextPostionX: function( pos, dx, posNext )
			{
				var s,mr;
				
				if(dx<0)
				{
					if( pos[1]>0 )	//same line
					{
						if( dx==-2 )
						{
							s= this._lineText(pos[0]).substring(0,pos[1]);
							mr=s.match( this.regGroupEnd );
							if(mr && mr[0])		//skip group
							{
								posNext[0]=pos[0];
								posNext[1]=pos[1]-mr[0].length;
							}
							else	//prev 1 char
							{
								posNext[0]=pos[0];
								posNext[1]=pos[1]-1;
							}
						}
						else	//prev 1 char
						{
							posNext[0]=pos[0];
							posNext[1]=pos[1]-1;
						}
					}
					else if( pos[0]>0 )		//not top line, move to end of prev line
					{
						posNext[0]=pos[0]-1;
						posNext[1]=this._lineText(posNext[0]).length;
					}
					else	//top line, copy
					{
						posNext[0]=pos[0];
						posNext[1]=pos[1];
					}
				}
				else
				{
					var ei= this._line(pos[0]);
					s= this.getTextContent(ei);
					
					if( pos[1]<s.length )		//same line
					{
						if( dx==2 )
						{
							s= s.slice( pos[1] );
							mr=s.match( this.regGroupHead );
							if(mr && mr[0])		//skip group
							{
								posNext[0]=pos[0];
								posNext[1]=pos[1]+mr[0].length;
							}
							else		//next 1 char
							{
								posNext[0]=pos[0];
								posNext[1]=pos[1]+1;
							}
						}
						else		//next 1 char
						{
							posNext[0]=pos[0];
							posNext[1]=pos[1]+1;
						}
					}
					else if( ei.nextSibling && ei.nextSibling.id!=this.eidTmp )	//not last line, move to next line head
					{
						posNext[0]=pos[0]+1;
						posNext[1]=0;
					}
					else	//last line, copy
					{
						posNext[0]=pos[0];
						posNext[1]=pos[1];
					}
				}
			},
			
			//nextPostionY: function( pos, dy, posNext )
			//pos: source
			//dy: line offset
			//posNext: dest/output
			//return length of the output line, -1 if unchanged and same as source.
			nextPostionY: function( pos, dy, posNext )
			{
				var n;
				
				if(dy<0 )
				{
					if(pos[0]>0)
					{
						posNext[0]=pos[0]+dy;
						if( posNext[0]<0 ) posNext[0]=0;
						
						n= this._lineText(posNext[0]).length;
						posNext[1]=(n>pos[1])?pos[1]:n;
						return n;
					}
					else		//copy
					{
						posNext[0]=pos[0];
						posNext[1]=pos[1];
						return -1;
					}
				}
				else
				{
					var num=this.GetLineNumber();
					if( pos[0]<num-1)
					{
						posNext[0]=pos[0]+dy;
						if( posNext[0]>=num ) posNext[0]=num-1;
						
						n= this._lineText(posNext[0]).length;
						posNext[1]=(n>pos[1])?pos[1]:n;
						return n;
					}
					else		//copy
					{
						posNext[0]=pos[0];
						posNext[1]=pos[1];
						return -1;
					}
				}
			},
			
			//LockCaret: function( [s] )
			//loack caret textarea
			//s: value to set the text of textarea
			LockCaret: function( s )
			{
				//$ec.TRACE("LockCaret");
				
				this.bLockCaret= true;
				
				if((typeof s)!="undefined")
				{
					var ei=$ec(this.eidCaret);
					ei.value=s;
					ei.select();
				}
			},
			
			UnlockCaret: function(bClear)
			{
				if(bClear)
				{
					var ei=$ec(this.eidCaret);
					ei.value="";
				}
				
				this.bLockCaret= false;
			},
			
			TabBlock: function( bShift )
			{
				if( this.nReadonly ) return;
				if( this.pos2[0]==-1 ) return;

				//$ec.STATUS(bShift);
				
				//prepare
				var pos,pos2,i,s;
				if( this.pos[0]<this.pos2[0] || ( this.pos[0]==this.pos2[0] && this.pos[1]<this.pos2[1] ) )
				{
					pos=this.pos; pos2=this.pos2;
				}
				else
				{
					pos=this.pos2; pos2=this.pos;
				}
				
				//if last line nCol=0, adjust to previous line
				if( pos2[1]==0 ) pos2[0]=pos2[0]-1;
				
				var nM= ( pos[0]==pos2[0])?0:1;		//multi line flag, 0:single line, 1:star, 2:,
				var ei= this._line(pos[0]);
				var eiLast=( pos[0]==pos2[0])?ei:this._line(pos2[0]);
				var k=pos[0];
				
				while(1)
				{
					s= this.getTextContent(ei).replace( this.regGetHeadSpace,"$1");
					ei.setAttribute("Stack","");
					
					if( bShift )
					{
						if(s.length>0)
						{
							if( nM==1 ) { this._saveop( ["_head"] ); nM=2; }
							i= s.length%4;
							if(i==0)i=4;
							this._op( k, 0, s.substring(0,i),"");
						}
					}
					else
					{
						if( nM==1 ) { this._saveop( ["_head"] ); nM=2; }
						i=4-(s.length%4);
						//$ec.STATUS(i);
						this._op( k, 0, "",(window.ActiveXObject)?this.spa[i]:this.spa[i].replace(/ /g,"\xA0") );
					}
					
					k++;
					
					if( ei==eiLast ) break;
					ei=ei.nextSibling;
				}
				if( nM==2 ) this._saveop( ["_end"] );
				
				if( pos[1]!=0 ){ pos[1]=0; this.updatePos(pos); }
				i=this.getTextContent(eiLast).length;
				if( pos2[1]!=i ){ pos2[1]=i; this.updatePos(pos2); }
				
				//reverse position to show caret at left-top
				if( this.pos===pos )
				{
					this.pos=pos2;
					this.pos2=pos;
				}
				
				this.FormatLine( pos[0] );
				
				this.ShowBlock();
				this.UpdateCaret(1);
			},
			
			//////////////////
			//input editing function group
			//////////////////
			
			ClearInput: function()
			{
				if( this.bLockCaret ) return;
				
				var bUpdate= !!this.sEditing;
				
				this.sEditing="";
				var ei=document.getElementById(this.eidCaret);
				ei.value="";
				ei.style.width="3px";
				ei.style.backgroundColor="transparent";
				this.UpdateCaret();
				
				if( bUpdate )
				{
					this._line(this.posEdit[0]).setAttribute("Stack","");
					this.FormatLine(this.posEdit[0]);
				}
			},
			
			CheckInput: function()
			{
				if( this.bLockCaret ) return;
				
				//for IME input state and normal input
				//mode1: some IME (such as MS-PINGYING), will firstly input alpha-char, then replace alpha-char to chinese char.
				//mode2: some IME directly input chinese/other-codeset char
				
				var ei=document.getElementById(this.eidCaret);
				if(ei.value )
				{
					if( ei.value!=this.sEditing )
					{
						var bMode1=0;
						
						if( !this.sEditing )
						{
							//this._copyPos( this.pos, this.posEdit );
							this.copyPos( this.getBlockHead(), this.posEdit );
							this.UpdateCaret(0,this.posEdit);
						}
						else
						{
							if( this.sEditing != ei.value.substring(0,this.sEditing.length ) )
							{
								//IME mode 1:
								
								//check if exist input matched
								var s= this.getTextContent($ec(this.eidText).childNodes[this.posEdit[0]]);
								if( this.posEdit[0]==this.pos[0] && s.substring(this.posEdit[1],this.pos[1])==this.sEditing )	//matched
								{
									//$ec.STATUS(this.posEdit+ " " + this.pos);
									this.SetBlock( this.pos[0], this.posEdit[1], this.pos[0], this.pos[1] );
									bMode1=1;
								}
								else
								{
									s= "IME input mismatch";
									this.ClearInput();
									$ec.THROW(s);
								}
							}
						}
						if( bMode1 )
						{
							this.InsertString(ei.value.replace(/ /g,"\xA0"),1,1 );
						}
						else
						{
							this.InsertString(ei.value.substring(this.sEditing.length,ei.value.length).replace(/ /g,"\xA0"),1,1 );
						}
						
						this.sEditing= ei.value;
						
						//resize caret
						if( window.ActiveXObject )
						{
							var r= ei.createTextRange();
							var b= r.getBoundingClientRect();
							//ei.style.width=(r.boundingWidth+3)+"px";
							ei.style.width=(r.boundingWidth+2)+"px";
						}
						else
						{
							/*
							*/
							var eTmp= this.getTmpLine();
							
							if( this.bTextContent) eTmp.childNodes[0].textContent=ei.value.replace(/ /g,"\xA0");
							else eTmp.childNodes[0].innerText=ei.value.replace(/ /g,"\xA0");
							
							ei.style.width= (eTmp.childNodes[1].offsetLeft-eTmp.offsetLeft+2)+"px";
						}
						
						if(!this.bDebug) ei.style.backgroundColor="white";
						
					}
				}
				else if(this.sEditing)
				{
					
				}
			},
			
			OnPropertyChange_ie: function(evt)
			{
				if( this.bLockCaret ) return;
				
				if( evt.propertyName=="value" )
				{
					//for IME input state or normal input state
					this.CheckInput();
				}
			},
			
			OnInput_mz: function(evt)
			{
				if( this.bLockCaret ) return;
				
				//for IME input state or normal input state
				this.CheckInput();
			},
			
			//////////////////
			//text content function group
			//////////////////
			
			dropData: function( posDrop, bCopy)
			{
				if( this.nReadonly ) return;
				if(this.pos2[0]==-1 ) return;
				
				if( this.isPosIn( posDrop ) )
				{
					if(!bCopy) return;
					if( ! (posDrop[0]==this.pos[0] && posDrop[1]==this.pos[1]) &&
						! (posDrop[0]==this.pos2[0] && posDrop[1]==this.pos2[1]) ) return;
				}
				
				var s= this.GetBlockString([]).join("\n");
				if( bCopy )
				{
					this.SetBlock( posDrop[0], posDrop[1], -1 );
					this.InsertString(s);
					this.copyPos( this.pos,this.pos2);
					this.copyPos( posDrop,this.pos);
					this.ShowBlock();
					this.UpdateCaret(1);
				}
				else
				{
					var pos,pos2;
					if( this.pos[0]<this.pos2[0] || ( this.pos[0]==this.pos2[0] && this.pos[1]<this.pos2[1] ) )
					{
						pos=this.pos; pos2=this.pos2;
					}
					else
					{
						pos=this.pos2; pos2=this.pos;
					}
					
					if( posDrop[0]>pos2[0] || ( posDrop[0]==pos2[0] && posDrop[1]>pos2[1] ) )
					{
						//adjust posDrop
						if( posDrop[0]==pos2[0] )
						{
							posDrop[1]=pos[1]+(posDrop[1]-pos2[1]);
						}
						if( pos[0]!=pos2[0] ) posDrop[0]=posDrop[0]-(pos2[0]-pos[0]);
						
					}
					
					this._saveop(["_head"]);
					this.DeleteBlock();
					this.SetBlock( posDrop[0], posDrop[1], -1 );
					this.InsertString(s);
					this._saveop(["_end"]);
					
					this.copyPos( this.pos,this.pos2);
					this.copyPos( posDrop,this.pos);
					
					//alert( this.pos+" "+this.pos2 );
					
					//window.setTimeout( "$ec.bind['"+this.eid+"'].SetBlock("+this.pos[0]+","+this.pos[1]+","+this.pos2[0]+","+this.pos2[1]+")", 1000 );
					/*
					*/
					
					//aaa
					this.updatePos(this.pos);
					
					//$ec.STATUS( this.pos+" "+this.pos2 );
					
					this.ShowBlock();
					this.UpdateCaret(1);
				}
			},
			
			//delete current block
			DeleteBlock: function(sKey)
			{
				if( this.nReadonly ) return;

				if( this.pos2[0]==-1 ) return;
				var pos=this.getBlockHead();
				
				var sDel= this.GetBlockString([]).join("\n");
				var r= this._op( pos[0], pos[1], sDel, "", 0, 0, sKey?sKey:"Del" );
				
				if(r)
				{
					this.SetBlock(r[0],r[1],-1,0);
					/*
					this.pos[0]=r[0];
					this.pos[1]=r[1];
					this._updatePos(this.pos );
					this.pos2[0]=-1;
					*/
					
					this.UpdateCaret(1);
					
					this._line(this.pos[0]).setAttribute("Stack","");
					this.FormatLine(this.pos[0]);
					
					//this.HideBlock();
				}
			},
			
			//InsertString: function(s [,bRaw [,bInput] ] )
			//bRaw: raw text data, text may contain [\t ]
			//bInput: internal use, from keyboard input
			//insert string at current block/caret
			InsertString: function(s,bRaw,bInput)
			{
				if( this.nReadonly ) return;
				
				//$ec.TRACE("15");
				
				var b= (this.pos2[0]==-1)?0:1;
				var pos=this.getBlockHead();
				var uidx=pos[0];	//update index
				
				//get del text
				var sDel;
				if(b)
				{
					sDel= this.GetBlockString([]).join("\n");
				}
				/*
				else if( this.bOverwrite && s.length==1 )
				{
					sDel= this._lineText(pos[0]).substring( pos[1], pos[1]+1 );
				}
				*/
				else if( this.bOverwrite )
				{
					sDel= this._lineText(pos[0]).substring( pos[1], pos[1]+s.length );
				}
				else sDel="";
				
				//$ec.TRACE("13");
				var r= this._op( pos[0], pos[1], sDel, (bRaw || window.ActiveXObject)?s:s.replace(/ /g,"\xA0"), bRaw, 0, "Inp" );
				//$ec.TRACE("14");
				
				if(r)
				{
					//$ec.TRACE("16");
					
					//$ec.TRACE(r);
					this.SetBlock( r[0], r[1], -1, 0 );
					
					//$ec.TRACE("17");
					
					//this.pos[0]=r[0];
					//this.pos[1]=r[1];
					//this._updatePos(this.pos );
					//this.pos2[0]=-1;
					
					/*
					var eLine= this._line();
					var ep= eLine.parentNode.parentNode;
					
					var eCaret=$ec(this.eidCaret);
					eCaret.style.left= (ep.offsetLeft+eLine.offsetLeft+this.pos[2]-(window.ActiveXObject?2:1))+"px";
					eCaret.style.top= (ep.offsetTop+eLine.offsetTop-eLine.parentNode.offsetTop+this.pos[3])+"px";
					*/
					
					//$ec.STATUS(this.pos+" "+this.pos2+" "+bInput);
					
					if( !bInput )
					{
						this.UpdateCaret(1);
						
						//window.setTimeout( "$ec.bind['"+this.eid+"'].UpdateCaret(1)",0 );
						//window.setTimeout( "$ec.bind['"+this.eid+"'].SetBlock( "+r[0]+", "+r[1]+", -1, 0 );$ec.bind['"+this.eid+"'].UpdateCaret(1)",0 );
						
						var ei;
						ei= this._line(uidx);
						ei.setAttribute("Stack","");
						//$ec.TRACE("20");
						
						this._line(r[0]).setAttribute("Stack","");
						
						//$ec.TRACE("19");
						
						this.FormatLine(uidx);
						//$ec.TRACE("18");
						//if(b) this.HideBlock();
					}
					else
					{
						var ei;
						ei= this._line(uidx);
						ei.setAttribute("Stack","");
						//$ec.TRACE("20");
						
						this._line(r[0]).setAttribute("Stack","");
						
						//$ec.TRACE("19");
						
						this.FormatLine(uidx,1);
						this._line(uidx).setAttribute("Stack","");
						
					}
				}
				
				if( this.sAgent=="firefox" && s.length>20000 ) this._designMode_ON_mz();
			},
			
			CopyData: function()
			{
				if( this.pos2[0]==-1 ) return;
				
				this.HideSelect();
				
				//if( window.ActiveXObject )
				if( window.clipboardData )
				{
					var s= this.GetBlockString([]).join("\n");
					if(s) window.clipboardData.setData("Text", s);
				}
				else
				{
					alert("This browser doesn't support the function.\n\nPlease use 'Ctrl+C' to copy text");
				}
			},
			
			CutData: function()
			{
				if( this.pos2[0]==-1 ) return;
				
				this.HideSelect();
				
				//if( window.ActiveXObject )
				if( window.clipboardData )
				{
					var s= this.GetBlockString([]).join("\n");
					if(s)
					{
						window.clipboardData.setData("Text", s);
						this.DeleteBlock();
					}
				}
				else
				{
					alert("This browser doesn't support the function.\n\nPlease use 'Ctrl+X' to cut text");
				}
			},
			
			PasteData: function()
			{
				this.HideSelect();
				
				//if( window.ActiveXObject )
				if( window.clipboardData )
				{
					var s= window.clipboardData.getData("Text");
					this.InsertString( s, 1 );
				}
				else
				{
					alert("This browser doesn't support the function.\n\nPlease use 'Ctrl+V' to paste text");
				}
			},
			
			//insert text, expand \n to match prefix spaces, as enter from keyboard.
			InsertText: function(s)
			{
				if(!s) return;
				
				if(s.indexOf("\n")>=0)
				{
					var pos=this.getBlockHead();
					
					var sTab= this._lineText( pos[0] ).substring(0,pos[1]).replace( this.regGetHeadSpace , "$1" );
					
					if( sTab.length>3)
					{
						if(sTab.length==pos[1] && s.charAt(0)=="\n" ) s=s.substring(1,s.length);
						
						sTab=sTab.substring(0,sTab.length-(sTab.length%4));
						s=s.replace( /\n/g, "\n"+sTab );
					}
				}
				
				this.InsertString(s);
			},
			
			CopyHyperText: function()
			{
				//alert("h");
				this.SetBlock( 0,0,-1 );	//remove tmp line text
				
				var RC= $ec.$import("org.supertree.editor.RuleCompiler").prototype;
				var sHint= "A new browser window in 'designMode' will be opened, you can select and copy the hyper text in new window.";
				if(this.sAgent=="safari" )
				{
					var wo= window.open( "about:blank", "_blank" );
					window.focus();
					alert(sHint);	//alert to delay, for safari
				}
				else
				{
					alert(sHint);
					var wo= window.open( "about:blank", "_blank" );
				}
				
				wo.document.open();
				
				var sHtml= "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">"+
					"<html xmlns=\"http://www.w3.org/1999/xhtml\">"+
					"<head>"+
					"<meta http-equiv=\"Content-Type\" content=\"text/html;charset=UTF-8\">"+
					"<style>"+RC.ColorStyle(RC.aColor,wo.document,1)+"</style>"+
					"</head>"+
					"<body contentEditable>"+
					"<b>Select the hyper text you need and copy them out. The hyper text can be copied out at least on <font color=green>IE6/Safari3.2/Chrome0.3</font>, but <font color=red>not on Firefox2/Opera9.6</font>.</b><br><br>"+
					$ec(this.eidText).parentNode.innerHTML+
					"</body>"+
					"</html>";
				//alert( sHtml.substring(0,100) );
				wo.document.write(sHtml);
				wo.document.close();
				
				wo.focus();
				
				wo.document.body.contentEditable=true;
				wo.document.designMode= 'on';
			},
			
			//////////////////
			//help function group
			//////////////////
			
			ShowHelp: function()
			{
				alert("Supertree Syntax Editor Help, v2.0\n"+
					"\n"+
					"-- Tested on IE6/Firefox2/Opera9.6/Safari3.2/Chrome0.3, P4 1.6G, 512M --\n"+
					"\n"+
					"By Feng WeiGuo, forxm@21cn.com, (86)-0755-81030955\n"+
					"\n"+
					"[Main Function]\n"+
					"\n"+
					"Cut, Copy, Paste, Drag & Drop, Undo, Redo, Find, Replace, Bookmark, Jump to Line,\n"+
					"Line Number/Line Break/Overwrite/Font Size/Bold/Wrap(read only),\n"+
					"\n"+
					"[Shortcut Keys]\n"+
					"\n"+
					"Ctrl+X, Ctrl+C, Ctrl+V, Ctrl+Drag & Drop, Ctrl+Z, Ctrl+Y, Ctrl+A, Ctrl+F, F3, Shift+F3, Ctrl+F2, F2, Shift+F2, Ctrl+G, "+
					"Ctrl+Del, Ctrl+Backspace, Tab (4 spaces), Shift+Tab, Insert, F1.\n"+
					"\n"+
					"[Other]\n"+
					"\n"+
					"All Tabs (\\u0009) will convert to 4 spaces (\\u0020).\n"+
					"All unicode spaces (\\u00A0) will convert to ascii spaces (\\u0020).\n"+
					"Use Del to delete single space, use Backspace to delete Tabs (4 spaces).\n"+
					"Syntax analysis for a line will stop if the line contains more than 5000 characters.\n"+
					"\n"+
					"[Developer]\n"+
					"\n"+
					"SetText()/GetText() interface.\n"+
					"Support self-defined syntax rule.\n"+
					"\n"+
					"Visit http://www.supertree.org for more information."
					);
			},
			
			ShowAbout: function()
			{
				alert("Supertree Syntax Editor V2.0\n"+
					"\n"+
					"Author: Feng WeiGuo (冯卫国)\n"+
					"\n"+
					"Web: www.supertree.org\n"+
					"     www.viviasoft.com\n"+
					"Email: forxm@163.com\n"+
					"Tel: (86)-0755-81030955\n"+
					"\n"+
					"Shenzhen, Guangdong, China.\n"+
					"2008-11\n"+
					"\n"+
					"Visit http://www.supertree.org for more information."
					);
			}
			
			
//////////////////////////////
// END EC-CLASS BODY    //NOTE - DO NOT CHANGE THESE 3 LINES, the ide wizards may use them.
//////////////////////////////
			}
		);
		
		//#EC_RES
		//#FILE: {id:"SyntaxEditorBackActive_gif",path:"SyntaxEditorBackActive.gif",dyna:1}
		//#FILE: {id:"DragMove_cur",path:"DRAGMOVE.CUR",dyna:1}
		//#FILE: {id:"DragCopy_cur",path:"DRAGCOPY.CUR",dyna:1}
		//#FILE: {id:"Bookmark_gif",path:"Bookmark.gif",dyna:1}
		//#END_EC_RES
		
		//#EC_MAP
		this.$map=[
			["","mousemove","OnMousemove"],
			["","scroll","OnScroll"],
			["caret","keydown","OnKeydown",1],
			["caret","keyup","OnKeyup",1],
			["text","dblclick","OnDblclick",1],
			["caret","dblclick","OnDblclick",1]
			];
		//#END_EC_MAP
		
	}
);
